From e6f4ff714c372d281582ea2f2280e9a63eaef92b Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 22 Nov 2018 16:37:03 -0800 Subject: [PATCH 001/272] Initial release (#3) Initial, working release of proto-plus for Python. This is a wrapper around [protocol buffers][1]. Protocol buffers is a specification format for APIs, such as those inside Google. This library provides protocol buffer message classes and objects that largely behave like native Python types. [1]: https://developers.google.com/protocol-buffers/ --- packages/proto-plus/.circleci/.pypirc.enc | 1 + packages/proto-plus/.circleci/config.yml | 177 +++++ packages/proto-plus/.coveragerc | 13 + packages/proto-plus/.flake8 | 8 + packages/proto-plus/.gitignore | 61 ++ packages/proto-plus/.readthedocs.yml | 6 + packages/proto-plus/CONTRIBUTING.rst | 22 + packages/proto-plus/LICENSE | 202 ++++++ packages/proto-plus/README.rst | 37 + packages/proto-plus/docs/Makefile | 20 + packages/proto-plus/docs/conf.py | 168 +++++ packages/proto-plus/docs/fields.rst | 122 ++++ packages/proto-plus/docs/index.rst | 43 ++ packages/proto-plus/docs/marshal.rst | 51 ++ packages/proto-plus/docs/messages.rst | 99 +++ packages/proto-plus/docs/reference/index.rst | 18 + .../proto-plus/docs/reference/marshal.rst | 5 + .../proto-plus/docs/reference/message.rst | 13 + packages/proto-plus/docs/status.rst | 20 + packages/proto-plus/nox.py | 56 ++ packages/proto-plus/proto/__init__.py | 67 ++ packages/proto-plus/proto/fields.py | 137 ++++ packages/proto-plus/proto/marshal/__init__.py | 13 + .../proto-plus/proto/marshal/containers.py | 40 ++ packages/proto-plus/proto/marshal/marshal.py | 364 ++++++++++ .../proto/marshal/types/__init__.py | 13 + .../proto-plus/proto/marshal/types/dates.py | 73 ++ .../proto-plus/proto/marshal/types/message.py | 39 ++ .../proto/marshal/types/wrappers.py | 83 +++ packages/proto-plus/proto/message.py | 637 ++++++++++++++++++ packages/proto-plus/proto/primitives.py | 37 + packages/proto-plus/proto/utils.py | 48 ++ packages/proto-plus/setup.py | 54 ++ packages/proto-plus/tests/conftest.py | 83 +++ .../proto-plus/tests/test_fields_bytes.py | 75 +++ .../proto-plus/tests/test_fields_composite.py | 93 +++ .../tests/test_fields_composite_string_ref.py | 107 +++ packages/proto-plus/tests/test_fields_enum.py | 194 ++++++ packages/proto-plus/tests/test_fields_int.py | 95 +++ .../tests/test_fields_map_composite.py | 102 +++ .../tests/test_fields_map_scalar.py | 58 ++ .../proto-plus/tests/test_fields_oneof.py | 49 ++ .../tests/test_fields_repeated_composite.py | 168 +++++ .../tests/test_fields_repeated_scalar.py | 114 ++++ .../proto-plus/tests/test_fields_string.py | 57 ++ .../proto-plus/tests/test_marshal_register.py | 53 ++ .../tests/test_marshal_types_dates.py | 228 +++++++ .../tests/test_marshal_types_message.py | 37 + .../tests/test_marshal_types_wrappers_bool.py | 109 +++ packages/proto-plus/tests/test_message.py | 191 ++++++ packages/proto-plus/tests/test_message_all.py | 65 ++ .../proto-plus/tests/test_message_nested.py | 59 ++ 52 files changed, 4684 insertions(+) create mode 100644 packages/proto-plus/.circleci/.pypirc.enc create mode 100644 packages/proto-plus/.circleci/config.yml create mode 100644 packages/proto-plus/.coveragerc create mode 100644 packages/proto-plus/.flake8 create mode 100644 packages/proto-plus/.gitignore create mode 100644 packages/proto-plus/.readthedocs.yml create mode 100644 packages/proto-plus/CONTRIBUTING.rst create mode 100644 packages/proto-plus/LICENSE create mode 100644 packages/proto-plus/README.rst create mode 100644 packages/proto-plus/docs/Makefile create mode 100644 packages/proto-plus/docs/conf.py create mode 100644 packages/proto-plus/docs/fields.rst create mode 100644 packages/proto-plus/docs/index.rst create mode 100644 packages/proto-plus/docs/marshal.rst create mode 100644 packages/proto-plus/docs/messages.rst create mode 100644 packages/proto-plus/docs/reference/index.rst create mode 100644 packages/proto-plus/docs/reference/marshal.rst create mode 100644 packages/proto-plus/docs/reference/message.rst create mode 100644 packages/proto-plus/docs/status.rst create mode 100644 packages/proto-plus/nox.py create mode 100644 packages/proto-plus/proto/__init__.py create mode 100644 packages/proto-plus/proto/fields.py create mode 100644 packages/proto-plus/proto/marshal/__init__.py create mode 100644 packages/proto-plus/proto/marshal/containers.py create mode 100644 packages/proto-plus/proto/marshal/marshal.py create mode 100644 packages/proto-plus/proto/marshal/types/__init__.py create mode 100644 packages/proto-plus/proto/marshal/types/dates.py create mode 100644 packages/proto-plus/proto/marshal/types/message.py create mode 100644 packages/proto-plus/proto/marshal/types/wrappers.py create mode 100644 packages/proto-plus/proto/message.py create mode 100644 packages/proto-plus/proto/primitives.py create mode 100644 packages/proto-plus/proto/utils.py create mode 100644 packages/proto-plus/setup.py create mode 100644 packages/proto-plus/tests/conftest.py create mode 100644 packages/proto-plus/tests/test_fields_bytes.py create mode 100644 packages/proto-plus/tests/test_fields_composite.py create mode 100644 packages/proto-plus/tests/test_fields_composite_string_ref.py create mode 100644 packages/proto-plus/tests/test_fields_enum.py create mode 100644 packages/proto-plus/tests/test_fields_int.py create mode 100644 packages/proto-plus/tests/test_fields_map_composite.py create mode 100644 packages/proto-plus/tests/test_fields_map_scalar.py create mode 100644 packages/proto-plus/tests/test_fields_oneof.py create mode 100644 packages/proto-plus/tests/test_fields_repeated_composite.py create mode 100644 packages/proto-plus/tests/test_fields_repeated_scalar.py create mode 100644 packages/proto-plus/tests/test_fields_string.py create mode 100644 packages/proto-plus/tests/test_marshal_register.py create mode 100644 packages/proto-plus/tests/test_marshal_types_dates.py create mode 100644 packages/proto-plus/tests/test_marshal_types_message.py create mode 100644 packages/proto-plus/tests/test_marshal_types_wrappers_bool.py create mode 100644 packages/proto-plus/tests/test_message.py create mode 100644 packages/proto-plus/tests/test_message_all.py create mode 100644 packages/proto-plus/tests/test_message_nested.py diff --git a/packages/proto-plus/.circleci/.pypirc.enc b/packages/proto-plus/.circleci/.pypirc.enc new file mode 100644 index 000000000000..7ec2f8900289 --- /dev/null +++ b/packages/proto-plus/.circleci/.pypirc.enc @@ -0,0 +1 @@ +Salted__kҝndzKp~)ćY@;eZs}@8(i09հHڽRٔ@W1 ֝4 \ No newline at end of file diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml new file mode 100644 index 000000000000..8d5e876ca744 --- /dev/null +++ b/packages/proto-plus/.circleci/config.yml @@ -0,0 +1,177 @@ +--- +version: 2 +workflows: + version: 2 + tests: + jobs: + - unit-3.5: + filters: + tags: + only: /.*/ + - unit-cpp-3.5: + filters: + tags: + only: /.*/ + - unit-3.6: + filters: + tags: + only: /.*/ + - unit-cpp-3.6: + filters: + tags: + only: /.*/ + - unit-3.7: + filters: + tags: + only: /.*/ + - unit-cpp-3.7: + filters: + tags: + only: /.*/ + - docs: + filters: + tags: + only: /.*/ + - publish: + requires: + - unit-3.5 + - unit-cpp-3.5 + - unit-3.6 + - unit-cpp-3.6 + - unit-3.7 + - unit-cpp-3.7 + - docs + filters: + branches: + ignore: /.*/ + tags: + only: /^v\d+\.\d+\.\d+((a|b|rc)\d+)?$/ +jobs: + unit-3.5: + docker: + - image: python:3.5 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install --pre nox-automation + pip install codecov + - run: + name: Run unit tests. + command: nox -s unit-3.5 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always + unit-3.6: + docker: + - image: python:3.6 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install --pre nox-automation + pip install codecov + - run: + name: Run unit tests. + command: nox -s unit-3.6 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always + unit-3.7: + docker: + - image: python:3.7 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install --pre nox-automation + pip install codecov + - run: + name: Run unit tests. + command: nox -s unit-3.7 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always + unit-cpp-3.5: + docker: + - image: python:3.5 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install --pre nox-automation + pip install codecov + - run: + name: Run unit tests. + command: nox -s unitcpp-3.5 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always + unit-cpp-3.6: + docker: + - image: python:3.6 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install --pre nox-automation + pip install codecov + - run: + name: Run unit tests. + command: nox -s unitcpp-3.6 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always + unit-cpp-3.7: + docker: + - image: python:3.7 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install --pre nox-automation + pip install codecov + - run: + name: Run unit tests. + command: nox -s unitcpp-3.7 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always + docs: + docker: + - image: python:3.6 + steps: + - checkout + - run: + name: Install nox. + command: pip install --pre nox-automation + - run: + name: Build the documentation. + command: nox -s docs + publish: + docker: + - image: python:3.7 + steps: + - checkout + - run: + name: Decrypt the PyPI key. + command: | + openssl aes-256-cbc -d \ + -in .circleci/.pypirc.enc \ + -out ~/.pypirc \ + -k "${PYPIRC_ENCRYPTION_KEY}" + - run: + name: Publish to PyPI. + command: python setup.py sdist upload diff --git a/packages/proto-plus/.coveragerc b/packages/proto-plus/.coveragerc new file mode 100644 index 000000000000..89353bb9897f --- /dev/null +++ b/packages/proto-plus/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True + +[report] +fail_under = 100 +show_missing = True +omit = + proto/marshal/containers.py +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ diff --git a/packages/proto-plus/.flake8 b/packages/proto-plus/.flake8 new file mode 100644 index 000000000000..fe63398b556d --- /dev/null +++ b/packages/proto-plus/.flake8 @@ -0,0 +1,8 @@ +[flake8] +ignore = + # Closing bracket mismatches opening bracket's line. + # This works poorly with type annotations in method declarations. + E123, E124 + # Line over-indented for visual indent. + # This works poorly with type annotations in method declarations. + E128, E131 diff --git a/packages/proto-plus/.gitignore b/packages/proto-plus/.gitignore new file mode 100644 index 000000000000..f2185a4d2b5a --- /dev/null +++ b/packages/proto-plus/.gitignore @@ -0,0 +1,61 @@ +*.py[cod] +*.sw[op] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.nox +.tox +.cache +.pytest_cache +htmlcov + +# Translations +*.mo + +# Mac +.DS_Store + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# JetBrains +.idea + +# Built documentation +docs/_build +docs/_build_doc2dash + +# Virtual environment +env/ +coverage.xml + +# System test environment variables. +system_tests/local_test_setup + +# Make sure a generated file isn't accidentally committed. +pylintrc +pylintrc.test diff --git a/packages/proto-plus/.readthedocs.yml b/packages/proto-plus/.readthedocs.yml new file mode 100644 index 000000000000..ebea21ff0c06 --- /dev/null +++ b/packages/proto-plus/.readthedocs.yml @@ -0,0 +1,6 @@ +--- +build: + image: latest +python: + pip_install: true + version: 3.6 diff --git a/packages/proto-plus/CONTRIBUTING.rst b/packages/proto-plus/CONTRIBUTING.rst new file mode 100644 index 000000000000..2e0d8efd6790 --- /dev/null +++ b/packages/proto-plus/CONTRIBUTING.rst @@ -0,0 +1,22 @@ +Contributing +============ + +We are thrilled that you are interested in contributing to this project. +Please open an issue or pull request with your ideas. + + +Contributor License Agreements +------------------------------ + +Before we can accept your pull requests, you will need to sign a Contributor +License Agreement (CLA): + +- **If you are an individual writing original source code** and **you own the + intellectual property**, then you'll need to sign an + `individual CLA `__. +- **If you work for a company that wants to allow you to contribute your work**, + then you'll need to sign a + `corporate CLA `__. + +You can sign these electronically (just scroll to the bottom). After that, +we'll be able to accept your pull requests. diff --git a/packages/proto-plus/LICENSE b/packages/proto-plus/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/packages/proto-plus/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst new file mode 100644 index 000000000000..de6883fcdb5f --- /dev/null +++ b/packages/proto-plus/README.rst @@ -0,0 +1,37 @@ +Proto Plus for Python +===================== + +|release level| |ci| |docs| |codecov| + + Beautiful, Pythonic protocol buffers. + +This is a wrapper around `protocol buffers`_. Protocol buffers is a +specification format for APIs, such as those inside Google. +This library provides protocol buffer message classes and objects that +largely behave like native Python types. + +.. _protocol buffers: https://developers.google.com/protocol-buffers/ + + +Documentation +------------- + +`Documentation`_ is available on Read the Docs. + +.. _documentation: https://proto-plus-python.readthedocs.io/ + + +Disclaimer +---------- + +This is not an official Google product. + + +.. |release level| image:: https://img.shields.io/badge/release%20level-alpha-red.svg?style=flat + :target: https://cloud.google.com/terms/launch-stages +.. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest + :target: https://gapic-generator-python.readthedocs.io/ +.. |ci| image:: https://circleci.com/gh/googleapis/proto-plus-python.svg?style=shield + :target: https://circleci.com/gh/googleapis/proto-plus-python +.. |codecov| image:: https://codecov.io/gh/googleapis/proto-plus-python/graph/badge.svg + :target: https://codecov.io/gh/googleapis/proto-plus-python diff --git a/packages/proto-plus/docs/Makefile b/packages/proto-plus/docs/Makefile new file mode 100644 index 000000000000..f9f9a54ca7dc --- /dev/null +++ b/packages/proto-plus/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = APIClientGeneratorforPython +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/packages/proto-plus/docs/conf.py b/packages/proto-plus/docs/conf.py new file mode 100644 index 000000000000..ad92d7f123f2 --- /dev/null +++ b/packages/proto-plus/docs/conf.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/stable/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + + +# -- Project information ----------------------------------------------------- + +project = 'Proto Plus for Python' +copyright = '2018, Google LLC' +author = 'Luke Sneeringer' + +# The short X.Y version +version = '0.1.0' +# The full version, including alpha/beta/rc tags +release = '0.1.0' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', + 'sphinx.ext.napoleon', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'proto-plus-python' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignmentAPIClientGeneratorforPython + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'proto-plus-python.tex', 'Proto Plus for Python Documentation', + 'Luke Sneeringer', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'proto-plus-python', 'Proto Plus for Python Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'proto-plus-python', 'Proto Plus for Python Documentation', + author, 'proto-plus-python', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/3/': None} diff --git a/packages/proto-plus/docs/fields.rst b/packages/proto-plus/docs/fields.rst new file mode 100644 index 000000000000..526d2973971e --- /dev/null +++ b/packages/proto-plus/docs/fields.rst @@ -0,0 +1,122 @@ +Fields +====== + +Fields are assigned using the :class:`~.Field` class, instantiated within a +:class:`~.Message` declaration. + +Fields always have a type (either a primitive, a message, or an enum) and a +``number``. + +.. code-block:: python + + import proto + + class Composer(proto.Message): + given_name = proto.Field(proto.STRING, number=1) + family_name = proto.Field(proto.STRING, number=2) + + class Song(proto.Message): + composer = proto.Field(Composer, number=1) + title = proto.Field(proto.STRING, number=2) + lyrics = proto.Field(proto.STRING, number=3) + year = proto.Field(proto.INT32, number=4) + + + +For messages and enums, assign the message or enum class directly (as shown +in the example above). + +.. note:: + + For messages declared in the same module, it is also possible to use a + string with the message class' name *if* the class is not + yet declared, which allows for declaring messages out of order or with + circular references. + +Repeated fields +--------------- + +Some fields are actually repeated fields. In protocol buffers, repeated fields +are generally equivalent to typed lists. In protocol buffers, these are +declared using the **repeated** keyword: + +.. code-block:: protobuf + + message Album { + repeated Song songs = 1; + string publisher = 2; + } + +Declare them in Python using the :class:`~.RepeatedField` class: + +.. code-block:: python + + class Album(proto.Message): + songs = proto.RepeatedField(Song, number=1) + publisher = proto.Field(proto.STRING, number=2) + + +Map fields +---------- + +Similarly, some fields are map fields. In protocol buffers, map fields are +equivalent to typed dictionaries, where the keys are either strings or +integers, and the values can be any type. In protocol buffers, these use +a special ``map`` syntax: + +.. code-block:: protobuf + + message Album { + map track_list = 1; + string publisher = 2; + } + +Declare them in Python using the :class:`~.MapField` class: + +.. code-block:: python + + class Album(proto.Message): + track_list = proto.MapField(proto.UINT32, Song, number=1) + publisher = proto.Field(proto.STRING, number=2) + + +Oneofs (mutually-exclusive fields) +---------------------------------- + +Protocol buffers allows certain fields to be declared as mutually exclusive. +This is done by wrapping fields in a ``oneof`` syntax: + +.. code-block:: protobuf + + import "google/type/postal_address.proto"; + + message AlbumPurchase { + Album album = 1; + oneof delivery { + google.type.PostalAddress postal_address = 2; + string download_uri = 3; + } + } + +When using this syntax, protocol buffers will enforce that only one of the +given fields is set on the message, and setting a field within the oneof +will clear any others. + +Declare this in Python using the ``oneof`` keyword-argument, which takes +a string (which should match for all fields within the oneof): + +.. code-block:: python + + from google.type.postal_address import PostalAddress + + class AlbumPurchase(proto.Message): + album = proto.Field(Album, number=1) + postal_address = proto.Field(PostalAddress, number=2, oneof='delivery') + download_uri = proto.Field(proto.STRING, number=3, oneof='delivery') + +.. warning:: + + ``oneof`` fields **must** be declared consecutively, otherwise the C + implementation of protocol buffers will reject the message. They need not + have consecutive field numbers, but they must be declared in consecutive + order. diff --git a/packages/proto-plus/docs/index.rst b/packages/proto-plus/docs/index.rst new file mode 100644 index 000000000000..7366bb1098cf --- /dev/null +++ b/packages/proto-plus/docs/index.rst @@ -0,0 +1,43 @@ +Proto Plus for Python +===================== + + Beautiful, Pythonic protocol buffers. + +This library provides a clean, readable, straightforward pattern for +declaraing messages in `protocol buffers`_. It provides a wrapper around +the official implementation, so that using messages feels natural while +retaining the power and flexibility of protocol buffers. + +.. warning:: + + This tool is a proof of concept and is being iterated on rapidly. + Feedback is welcome, but please do not try to use this in some kind of + system where stability is an expectation. + +.. _protocol buffers: https://developers.google.com/protocol-buffers/ + + +Installing +---------- + +Install this library using ``pip``: + +.. code-block:: shell + + $ pip install proto-plus + +This library carries a dependency on the official implementation +(``protobuf``), which may install a C component. + + +Table of Contents +----------------- + +.. toctree:: + :maxdepth: 2 + + messages + fields + marshal + status + reference/index diff --git a/packages/proto-plus/docs/marshal.rst b/packages/proto-plus/docs/marshal.rst new file mode 100644 index 000000000000..64a72f74b02b --- /dev/null +++ b/packages/proto-plus/docs/marshal.rst @@ -0,0 +1,51 @@ +Type Marshaling +=============== + +Proto Plus provides a service that converts between protocol buffer objects +and native Python types (or the wrapper types provided by this library). + +This allows native Python objects to be used in place of protocol buffer +messages where appropriate. In all cases, we return the native type, and are +liberal on what we accept. + +Well-known types +---------------- + +The following types are currently handled by Proto Plus: + +=================================== ======================= ======== +Protocol buffer type Python type Nullable +=================================== ======================= ======== +``google.protobuf.BoolValue`` ``bool`` Yes +``google.protobuf.BytesValue`` ``bytes`` Yes +``google.protobuf.DoubleValue`` ``float`` Yes +``google.protobuf.Duration`` ``datetime.timedelta`` – +``google.protobuf.FloatValue`` ``float`` Yes +``google.protobuf.Int32Value`` ``int`` Yes +``google.protobuf.Int64Value`` ``int`` Yes +``google.protobuf.StringValue`` ``str`` Yes +``google.protobuf.Timestamp`` ``datetime.datetime`` Yes +``google.protobuf.UInt32Value`` ``int`` Yes +``google.protobuf.UInt64Value`` ``int`` Yes +=================================== ======================= ======== + +.. note:: + + Protocol buffers include well-known types for ``Timestamp`` and + ``Duration``, both of which have nanosecond precision. However, the + Python ``datetime`` and ``timedelta`` objects have only microsecond + precision. + + If you *write* a timestamp field using a Python ``datetime`` value, + any existing nanosecond precision will be overwritten. + + +Wrapper types +------------- + +Additionally, every :class:`~.Message` subclass is a wrapper class. The +creation of the class also creates the underlying protocol buffer class, and +this is registered to the marshal. + +The underlying protocol buffer message class is accessible with the +:meth:`~.Message.pb` class method. diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst new file mode 100644 index 000000000000..24aa6d5d9c32 --- /dev/null +++ b/packages/proto-plus/docs/messages.rst @@ -0,0 +1,99 @@ +Messages +======== + +The fundamental building block in protocol buffers are `messages`_. +Messages are essentially permissive, strongly-typed structs (dictionaries), +which have zero or more fields that may themselves contain primitives or +other messages. + +.. code-block:: protobuf + + syntax = "proto3"; + + message Song { + Composer composer = 1; + string title = 2; + string lyrics = 3; + int32 year = 4; + } + + message Composer { + string given_name = 1; + string family_name = 2; + } + +The most common use case for protocol buffers is to write a ``.proto`` file, +and then use the protocol buffer compiler to generate code for it. + + +Declaring messages +------------------ + +However, it is possible to declare messages directly. +This is the equivalent message declaration in Python, using this library: + +.. code-block:: python + + import proto + + class Composer(proto.Message): + given_name = proto.Field(proto.STRING, number=1) + family_name = proto.Field(proto.STRING, number=2) + + class Song(proto.Message): + composer = proto.Field(Composer, number=1) + title = proto.Field(proto.STRING, number=2) + lyrics = proto.Field(proto.STRING, number=3) + year = proto.Field(proto.INT32, number=4) + +A few things to note: + +* This library only handles proto3. +* The ``number`` is really a field ID. It is *not* a value of any kind. +* All fields are optional (as is always the case in proto3). As a general + rule, there is no distinction between setting the type's falsy value and + not setting it at all (although there are exceptions to this in some cases). + +.. _messages: https://developers.google.com/protocol-buffers/docs/proto3#simple + +Messages are fundamentally made up of :doc:`fields`. Most messages are nothing +more than a name and their set of fields. + + +Usage +----- + +Instantiate messages using either keyword arguments or a :class:`dict` +(and mix and matching is acceptable): + +.. code-block:: python + + >>> song = Song( + ... composer={'given_name': 'Johann', 'family_name': 'Pachelbel'}, + ... title='Canon in D', + ... year=1680, + ... ) + >>> song.composer.family_name + 'Pachelbel' + >>> song.title + 'Canon in D' + +Serialization +------------- + +Serialization and deserialization is available through the +:meth:`~.Message.serialize` and :meth:`~.Message.deserialize` class methods. + +The :meth:`~.Message.serialize` method is available on the message *classes* +only, and accepts an instance: + +.. code-block:: python + + serialized_song = Song.serialize(song) + +The :meth:`~.Message.deserialize` method accepts a :class:`bytes`, and +returns an instance of the message: + +.. code-block:: python + + song = Song.deserialize(serialized_song) diff --git a/packages/proto-plus/docs/reference/index.rst b/packages/proto-plus/docs/reference/index.rst new file mode 100644 index 000000000000..85af2afe6edb --- /dev/null +++ b/packages/proto-plus/docs/reference/index.rst @@ -0,0 +1,18 @@ +Reference +--------- + +Below is a reference for the major classes and functions within this +module. + +It is split into two main sections: + +- The :doc:`message` section (which uses the ``message`` and ``fields`` + modules) handles constructing messages. +- The :doc:`marshal` module handles translating between internal protocol + buffer instances and idiomatic equivalents. + +.. toctree:: + :maxdepth: 2 + + message + marshal diff --git a/packages/proto-plus/docs/reference/marshal.rst b/packages/proto-plus/docs/reference/marshal.rst new file mode 100644 index 000000000000..73caec4d4c0e --- /dev/null +++ b/packages/proto-plus/docs/reference/marshal.rst @@ -0,0 +1,5 @@ +Marshal +------- + +.. automodule:: proto.marshal + :members: diff --git a/packages/proto-plus/docs/reference/message.rst b/packages/proto-plus/docs/reference/message.rst new file mode 100644 index 000000000000..4fe8273fd023 --- /dev/null +++ b/packages/proto-plus/docs/reference/message.rst @@ -0,0 +1,13 @@ +Message and Field +----------------- + +.. autoclass:: proto.message.Message + :members: + + .. automethod:: pb + .. automethod:: serialize + .. automethod:: deserialize + + +.. automodule:: proto.fields + :members: diff --git a/packages/proto-plus/docs/status.rst b/packages/proto-plus/docs/status.rst new file mode 100644 index 000000000000..9fefa35550c2 --- /dev/null +++ b/packages/proto-plus/docs/status.rst @@ -0,0 +1,20 @@ +Status +====== + +Features and Limitations +------------------------ + +Nice things this library does: + +- Idiomatic protocol buffer message representation and usage. +- Wraps the official protocol buffers implementation, and exposes its objects + in the public API so that they are available where needed. + + +Upcoming work +------------- + +- Improved enum support. +- Specialized behavior for ``google.protobuf.Value`` and + ``google.protobuf.Struct`` objects. +- Specialized behavior for ``google.protobuf.FieldMask`` objects. diff --git a/packages/proto-plus/nox.py b/packages/proto-plus/nox.py new file mode 100644 index 000000000000..b82cc7cd4221 --- /dev/null +++ b/packages/proto-plus/nox.py @@ -0,0 +1,56 @@ +# Copyright 2017, Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import os + +import nox + + +@nox.session(python=['3.5', '3.6', '3.7']) +def unit(session, proto='python'): + """Run the unit test suite.""" + + session.env['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = proto + session.install('coverage', 'pytest', 'pytest-cov') + session.install('-e', '.') + + session.run( + 'py.test', + '-W=error', + '--quiet', + '--cov=proto', + '--cov-config=.coveragerc', + '--cov-report=term', + '--cov-report=html', + os.path.join('tests', ''), + ) + + +@nox.session(python=['3.5', '3.6', '3.7']) +def unitcpp(session): + return unit(session, proto='cpp') + + +@nox.session(python='3.6') +def docs(session): + """Build the docs.""" + + session.install('sphinx', 'sphinx_rtd_theme') + session.install('.') + + # Build the docs! + session.run('rm', '-rf', 'docs/_build/') + session.run('sphinx-build', '-W', '-b', 'html', '-d', + 'docs/_build/doctrees', 'docs/', 'docs/_build/html/') diff --git a/packages/proto-plus/proto/__init__.py b/packages/proto-plus/proto/__init__.py new file mode 100644 index 000000000000..8f69ef55c21a --- /dev/null +++ b/packages/proto-plus/proto/__init__.py @@ -0,0 +1,67 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .fields import Field +from .fields import MapField +from .fields import RepeatedField +from .marshal.marshal import marshal +from .message import Message +from .primitives import ProtoType + + +DOUBLE = ProtoType.DOUBLE +FLOAT = ProtoType.FLOAT +INT64 = ProtoType.INT64 +UINT64 = ProtoType.UINT64 +INT32 = ProtoType.INT32 +FIXED64 = ProtoType.FIXED64 +FIXED32 = ProtoType.FIXED32 +BOOL = ProtoType.BOOL +STRING = ProtoType.STRING +MESSAGE = ProtoType.MESSAGE +BYTES = ProtoType.BYTES +UINT32 = ProtoType.UINT32 +ENUM = ProtoType.ENUM +SFIXED32 = ProtoType.SFIXED32 +SFIXED64 = ProtoType.SFIXED64 +SINT32 = ProtoType.SINT32 +SINT64 = ProtoType.SINT64 + + +__all__ = ( + 'Field', + 'MapField', + 'RepeatedField', + 'marshal', + 'Message', + + # Expose the types directly. + 'DOUBLE', + 'FLOAT', + 'INT64', + 'UINT64', + 'INT32', + 'FIXED64', + 'FIXED32', + 'BOOL', + 'STRING', + 'MESSAGE', + 'BYTES', + 'UINT32', + 'ENUM', + 'SFIXED32', + 'SFIXED64', + 'SINT32', + 'SINT64', +) diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py new file mode 100644 index 000000000000..097640cc935e --- /dev/null +++ b/packages/proto-plus/proto/fields.py @@ -0,0 +1,137 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.protobuf import descriptor_pb2 + +from proto.primitives import ProtoType + + +class Field: + """A representation of a type of field in protocol buffers.""" + + def __init__(self, proto_type, *, number: int, + message=None, enum=None, oneof: str = None, + json_name: str = None): + # This class is not intended to stand entirely alone; + # data is augmented by the metaclass for Message. + self.mcls_data = {} + self.parent = None + + # If the proto type sent is an object or a string, it is really + # a message or enum. + if not isinstance(proto_type, int): + message = proto_type + proto_type = ProtoType.MESSAGE + + # Save the direct arguments. + self.number = number + self.proto_type = proto_type + self.message = message + self.enum = enum + self.oneof = oneof + self.json_name = json_name + + # Fields are neither repeated nor maps. + # The RepeatedField and MapField subclasses override these values + # in their initializers. + self.repeated = False + + # Once the descriptor is accessed the first time, cache it. + # This is important because in rare cases the message or enum + # types are written later. + self._descriptor = None + + @property + def descriptor(self): + """Return the descriptor for the field.""" + if not self._descriptor: + # Resolve the message type, if any, to a string. + type_name = None + if isinstance(self.message, str): + if not self.message.startswith(self.package): + self.message = '{package}.{name}'.format( + package=self.package, + name=self.message, + ) + type_name = self.message + elif self.message: + if hasattr(self.message, 'DESCRIPTOR'): + type_name = self.message.DESCRIPTOR.full_name + else: + type_name = self.message.meta.full_name + elif self.enum: + # FIXME: This is obviously wrong (however, it does *work*). + # We need to set the enum type name and add the enum to + # the descriptor (like with messages above). + self.proto_type = ProtoType.INT32 + + # Set the descriptor. + self._descriptor = descriptor_pb2.FieldDescriptorProto( + name=self.name, + number=self.number, + label=3 if self.repeated else 1, + type=self.proto_type, + type_name=type_name, + json_name=self.json_name, + ) + + # Return the descriptor. + return self._descriptor + + @property + def name(self) -> str: + """Return the name of the field.""" + return self.mcls_data['name'] + + @property + def package(self) -> str: + """Return the package of the field.""" + return self.mcls_data['package'] + + @property + def pb_type(self): + """Return the composite type of the field, or None for primitives.""" + # For primitives, return None. + if not self.message: + return None + + # Return the internal protobuf message. + if hasattr(self.message, '_meta'): + return self.message.pb() + return self.message + + +class RepeatedField(Field): + """A representation of a repeated field in protocol buffers.""" + + def __init__(self, proto_type, *, number: int, + message=None, enum=None): + super().__init__(proto_type, number=number, message=message, enum=enum) + self.repeated = True + + +class MapField(Field): + """A representation of a map field in protocol buffers.""" + + def __init__(self, key_type, value_type, *, number: int, + message=None, enum=None): + super().__init__(value_type, number=number, message=message, enum=enum) + self.map_key_type = key_type + + +__all__ = ( + 'Field', + 'MapField', + 'RepeatedField', +) diff --git a/packages/proto-plus/proto/marshal/__init__.py b/packages/proto-plus/proto/marshal/__init__.py new file mode 100644 index 000000000000..b0c7da3d7725 --- /dev/null +++ b/packages/proto-plus/proto/marshal/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/packages/proto-plus/proto/marshal/containers.py b/packages/proto-plus/proto/marshal/containers.py new file mode 100644 index 000000000000..28813c34fd6c --- /dev/null +++ b/packages/proto-plus/proto/marshal/containers.py @@ -0,0 +1,40 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file pulls in the container types from internal protocol buffers, +# and exports the types available. +# +# If the C extensions were not installed, then their container types will +# not be included. + +from google.protobuf.internal import containers +try: + from google.protobuf.pyext import _message +except ImportError: + _message = None + +repeated_composite_types = (containers.RepeatedCompositeFieldContainer,) +repeated_scalar_types = (containers.RepeatedScalarFieldContainer,) +map_composite_types = (containers.MessageMap,) +if _message: + repeated_composite_types += (_message.RepeatedCompositeContainer,) + repeated_scalar_types += (_message.RepeatedScalarContainer,) + map_composite_types += (_message.MessageMapContainer,) + + +__all__ = ( + 'repeated_composite_types', + 'repeated_scalar_types', + 'map_composite_types', +) diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py new file mode 100644 index 000000000000..1cc4fcc7ea16 --- /dev/null +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -0,0 +1,364 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import abc +import collections +import copy + +from google.protobuf import message +from google.protobuf import duration_pb2 +from google.protobuf import timestamp_pb2 +from google.protobuf import wrappers_pb2 + +from proto.marshal import containers +from proto.marshal.types import dates +from proto.marshal.types import wrappers +from proto.utils import cached_property + + +class Rule(abc.ABC): + """Abstract class definition for marshal rules.""" + + @classmethod + def __subclasshook__(cls, C): + if hasattr(C, 'to_python') and hasattr(C, 'to_proto'): + return True + return NotImplemented + + +class MarshalRegistry: + """A class to translate between protocol buffers and Python classes. + + Protocol buffers defines many common types (e.g. Timestamp, Duration) + which also exist in the Python standard library. The marshal essentially + translates between these: it keeps a registry of common protocol buffers + and their Python representations, and translates back and forth. + + The protocol buffer class is always the "key" in this relationship; when + presenting a message, the declared field types are used to determine + whether a value should be transformed into another class. Similarly, + when accepting a Python value (when setting a field, for example), + the declared field type is still used. This means that, if appropriate, + multiple protocol buffer types may use the same Python type. + + The marshal is intended to be a singleton; this module instantiates + and exports one marshal, which is imported throughout the rest of this + library. This allows for an advanced case where user code registers + additional types to be marshaled. + """ + def __init__(self): + self._registry = {} + self._noop = NoopMarshal() + self.reset() + + def register(self, proto_type: type, rule: Rule = None): + """Register a rule against the given ``proto_type``. + + This function expects a ``proto_type`` (the descriptor class) and + a ``rule``; an object with a ``to_python`` and ``to_proto`` method. + Each method should return the appropriate Python or protocol buffer + type, and be idempotent (e.g. accept either type as input). + + This function can also be used as a decorator:: + + @marshal.register(timestamp_pb2.Timestamp) + class TimestampMarshal: + ... + + In this case, the class will be initialized for you with zero + arguments. + + Args: + proto_type (type): A protocol buffer message type. + rule: A marshal object + """ + # Sanity check: Do not register anything to a class that is not + # a protocol buffer message. + if not issubclass(proto_type, message.Message): + raise TypeError('Only protocol buffer messages may be registered ' + 'to the marshal.') + + # If a rule was provided, register it and be done. + if rule: + # Ensure the rule implements Rule. + if not isinstance(rule, Rule): + raise TypeError('Marshal rule instances must implement ' + '`to_proto` and `to_python` methods.') + + # Register the rule. + self._registry[proto_type] = rule + return + + # Create an inner function that will register an instance of the + # marshal class to this object's registry, and return it. + def register_rule_class(rule_class: type): + # Ensure the rule class is a valid rule. + if not issubclass(rule_class, Rule): + raise TypeError('Marshal rule subclasses must implement ' + '`to_proto` and `to_python` methods.') + + # Register the rule class. + self._registry[proto_type] = rule_class() + return rule_class + return register_rule_class + + def reset(self): + """Reset the registry to its initial state.""" + self._registry.clear() + + # Register date and time wrappers. + self.register(timestamp_pb2.Timestamp, dates.TimestampMarshal()) + self.register(duration_pb2.Duration, dates.DurationMarshal()) + + # Register nullable primitive wrappers. + self.register(wrappers_pb2.BoolValue, wrappers.BoolValueMarshal()) + self.register(wrappers_pb2.BytesValue, wrappers.BytesValueMarshal()) + self.register(wrappers_pb2.DoubleValue, wrappers.DoubleValueMarshal()) + self.register(wrappers_pb2.FloatValue, wrappers.FloatValueMarshal()) + self.register(wrappers_pb2.Int32Value, wrappers.Int32ValueMarshal()) + self.register(wrappers_pb2.Int64Value, wrappers.Int64ValueMarshal()) + self.register(wrappers_pb2.StringValue, wrappers.StringValueMarshal()) + self.register(wrappers_pb2.UInt32Value, wrappers.UInt32ValueMarshal()) + self.register(wrappers_pb2.UInt64Value, wrappers.UInt64ValueMarshal()) + + def to_python(self, proto_type, value, *, absent: bool = None): + # Internal protobuf has its own special type for lists of values. + # Return a view around it that implements MutableSequence. + if isinstance(value, containers.repeated_composite_types): + return RepeatedComposite(value) + if isinstance(value, containers.repeated_scalar_types): + return Repeated(value) + + # Same thing for maps of messages. + if isinstance(value, containers.map_composite_types): + return MapComposite(value) + + # Convert ordinary values. + rule = self._registry.get(proto_type, self._noop) + return rule.to_python(value, absent=absent) + + def to_proto(self, proto_type, value, *, strict: bool = False): + # For our repeated and map view objects, simply return the + # underlying pb. + if isinstance(value, (Repeated, MapComposite)): + return value.pb + + # Convert lists and tuples recursively. + if isinstance(value, (list, tuple)): + return type(value)([self.to_proto(proto_type, i) for i in value]) + + # Convert dictionaries recursively when the proto type is a map. + # This is slightly more complicated than converting a list or tuple + # because we have to step through the magic that protocol buffers does. + # + # Essentially, a type of map will show up here as + # a FoosEntry with a `key` field, `value` field, and a `map_entry` + # annotation. We need to do the conversion based on the `value` + # field's type. + if isinstance(value, dict) and (proto_type.DESCRIPTOR.has_options and + proto_type.DESCRIPTOR.GetOptions().map_entry): + return {k: self.to_proto(type(proto_type().value), v) + for k, v in value.items()} + + # Convert ordinary values. + rule = self._registry.get(proto_type, self._noop) + pb_value = rule.to_proto(value) + + # Sanity check: If we are in strict mode, did we get the value we want? + if strict and not isinstance(pb_value, proto_type): + raise TypeError( + 'Parameter must be instance of the same class; ' + 'expected {expected}, got {got}'.format( + expected=proto_type.__name__, + got=pb_value.__class__.__name__, + ), + ) + + # Return the final value. + return pb_value + + +class Repeated(collections.MutableSequence): + """A view around a mutable sequence in protocol buffers. + + This implements the full Python MutableSequence interface, but all methods + modify the underlying field container directly. + """ + def __init__(self, sequence): + self._pb = sequence + + def __copy__(self): + """Copy this object and return the copy.""" + return type(self)(sequence=self.pb[:]) + + def __delitem__(self, key): + """Delete the given item.""" + del self.pb[key] + + def __eq__(self, other): + if hasattr(other, 'pb'): + return tuple(self.pb) == tuple(other.pb) + return tuple(self.pb) == tuple(other) + + def __getitem__(self, key): + """Return the given item.""" + return self.pb[key] + + def __len__(self): + """Return the length of the sequence.""" + return len(self.pb) + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return repr(self.pb) + + def __setitem__(self, key, value): + self.pb[key] = value + + def insert(self, index: int, value): + """Insert ``value`` in the sequence before ``index``.""" + self.pb.insert(index, value) + + def sort(self, *, key: str = None, reverse: bool = False): + """Stable sort *IN PLACE*.""" + self.pb.sort(key=key, reverse=reverse) + + @property + def pb(self): + return self._pb + + +class RepeatedComposite(Repeated): + """A view around a mutable sequence of messages in protocol buffers. + + This implements the full Python MutableSequence interface, but all methods + modify the underlying field container directly. + """ + @cached_property + def _pb_type(self): + """Return the protocol buffer type for this sequence.""" + # There is no public-interface mechanism to determine the type + # of what should go in the list (and the C implementation seems to + # have no exposed mechanism at all). + # + # If the list has members, use the existing list members to + # determine the type. + if len(self.pb) > 0: + return type(self.pb[0]) + + # We have no members in the list. + # In order to get the type, we create a throw-away copy and add a + # blank member to it. + canary = copy.deepcopy(self.pb).add() + return type(canary) + + def __getitem__(self, key): + return marshal.to_python(self._pb_type, self.pb[key]) + + def __setitem__(self, key, value): + pb_value = marshal.to_proto(self._pb_type, value, strict=True) + + # Protocol buffers does not define a useful __setitem__, so we + # have to pop everything after this point off the list and reload it. + after = [pb_value] + while self.pb[key:]: + after.append(self.pb.pop(key)) + self.pb.extend(after) + + def insert(self, index: int, value): + """Insert ``value`` in the sequence before ``index``.""" + pb_value = marshal.to_proto(self._pb_type, value, strict=True) + + # Protocol buffers does not define a useful insert, so we have + # to pop everything after this point off the list and reload it. + after = [pb_value] + while self.pb[index:]: + after.append(self.pb.pop(index)) + self.pb.extend(after) + + +class MapComposite(collections.MutableMapping): + """A view around a mutable sequence in protocol buffers. + + This implements the full Python MutableMapping interface, but all methods + modify the underlying field container directly. + """ + @cached_property + def _pb_type(self): + """Return the protocol buffer type for this sequence.""" + # Huzzah, another hack. Still less bad than RepeatedComposite. + return type(self.pb.GetEntryClass()().value) + + def __init__(self, sequence): + self._pb = sequence + + def __contains__(self, key): + # Protocol buffers is so permissive that querying for the existence + # of a key will in of itself create it. + # + # By taking a tuple of the keys and querying that, we avoid sending + # the lookup to protocol buffers and therefore avoid creating the key. + return key in tuple(self.keys()) + + def __getitem__(self, key): + # We handle raising KeyError ourselves, because otherwise protocol + # buffers will create the key if it does not exist. + if key not in self: + raise KeyError(key) + return marshal.to_python(self._pb_type, self.pb[key]) + + def __setitem__(self, key, value): + pb_value = marshal.to_proto(self._pb_type, value, strict=True) + + # Directly setting a key is not allowed; however, protocol buffers + # is so permissive that querying for the existence of a key will in + # of itself create it. + # + # Therefore, we create a key that way (clearing any fields that may + # be set) and then merge in our values. + self.pb[key].Clear() + self.pb[key].MergeFrom(pb_value) + + def __delitem__(self, key): + self.pb.pop(key) + + def __len__(self): + return len(self.pb) + + def __iter__(self): + return iter(self.pb) + + @property + def pb(self): + return self._pb + + +class NoopMarshal: + """A catch-all marshal that does nothing.""" + + def to_python(self, pb_value, *, absent: bool = None): + return pb_value + + def to_proto(self, value): + return value + + +marshal = MarshalRegistry() + +__all__ = ( + 'marshal', +) diff --git a/packages/proto-plus/proto/marshal/types/__init__.py b/packages/proto-plus/proto/marshal/types/__init__.py new file mode 100644 index 000000000000..b0c7da3d7725 --- /dev/null +++ b/packages/proto-plus/proto/marshal/types/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/packages/proto-plus/proto/marshal/types/dates.py b/packages/proto-plus/proto/marshal/types/dates.py new file mode 100644 index 000000000000..2f54d274fb83 --- /dev/null +++ b/packages/proto-plus/proto/marshal/types/dates.py @@ -0,0 +1,73 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from datetime import datetime +from datetime import timedelta +from datetime import timezone + +from google.protobuf import duration_pb2 +from google.protobuf import timestamp_pb2 + + +class TimestampMarshal: + """A marshal between Python datetimes and protobuf timestamps. + + Note: Python datetimes are less precise than protobuf datetimes + (microsecond vs. nanosecond level precision). If nanosecond-level + precision matters, it is recommended to interact with the internal + proto directly. + """ + def to_python(self, value, *, absent: bool = None) -> datetime: + if isinstance(value, timestamp_pb2.Timestamp): + if absent: + return None + return datetime.fromtimestamp( + value.seconds + value.nanos / 1e9, + tz=timezone.utc, + ) + return value + + def to_proto(self, value) -> timestamp_pb2.Timestamp: + if isinstance(value, datetime): + return timestamp_pb2.Timestamp( + seconds=int(value.timestamp()), + nanos=value.microsecond * 1000, + ) + return value + + +class DurationMarshal: + """A marshal between Python timedeltas and protobuf durations. + + Note: Python timedeltas are less precise than protobuf durations + (microsecond vs. nanosecond level precision). If nanosecond-level + precision matters, it is recommended to interact with the internal + proto directly. + """ + def to_python(self, value, *, absent: bool = None) -> timedelta: + if isinstance(value, duration_pb2.Duration): + return timedelta( + days=value.seconds // 86400, + seconds=value.seconds % 86400, + microseconds=value.nanos // 1000, + ) + return value + + def to_proto(self, value) -> duration_pb2.Duration: + if isinstance(value, timedelta): + return duration_pb2.Duration( + seconds=value.days * 86400 + value.seconds, + nanos=value.microseconds * 1000, + ) + return value diff --git a/packages/proto-plus/proto/marshal/types/message.py b/packages/proto-plus/proto/marshal/types/message.py new file mode 100644 index 000000000000..c74d496df13e --- /dev/null +++ b/packages/proto-plus/proto/marshal/types/message.py @@ -0,0 +1,39 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class MessageMarshal: + """A marshal for converting between a descriptor and proto.Message.""" + + def __init__(self, descriptor: type, wrapper: type): + self._descriptor = descriptor + self._wrapper = wrapper + + def to_python(self, value, *, absent: bool = None): + if isinstance(value, self._descriptor): + return self._wrapper.wrap(value) + return value + + def to_proto(self, value): + if isinstance(value, self._wrapper): + return self._wrapper.pb(value) + if isinstance(value, dict) and not self.is_map: + return self._descriptor(**value) + return value + + @property + def is_map(self): + """Return True if the descriptor is a map entry, False otherwise.""" + desc = self._descriptor.DESCRIPTOR + return desc.has_options and desc.GetOptions().map_entry diff --git a/packages/proto-plus/proto/marshal/types/wrappers.py b/packages/proto-plus/proto/marshal/types/wrappers.py new file mode 100644 index 000000000000..5d2d3658d704 --- /dev/null +++ b/packages/proto-plus/proto/marshal/types/wrappers.py @@ -0,0 +1,83 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.protobuf import wrappers_pb2 + + +class WrapperMarshal: + """A marshal for converting the protobuf wrapper classes to Python. + + This class converts between ``google.protobuf.BoolValue``, + ``google.protobuf.StringValue``, and their siblings to the appropriate + Python equivalents. + + These are effectively similar to the protobuf primitives except + that None becomes a possible value. + """ + def to_python(self, value, *, absent: bool = None): + if isinstance(value, self._proto_type): + if absent: + return None + return value.value + return value + + def to_proto(self, value): + if isinstance(value, self._python_type): + return self._proto_type(value=value) + return value + + +class DoubleValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.DoubleValue + _python_type = float + + +class FloatValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.FloatValue + _python_type = float + + +class Int64ValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.Int64Value + _python_type = int + + +class UInt64ValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.UInt64Value + _python_type = int + + +class Int32ValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.Int32Value + _python_type = int + + +class UInt32ValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.UInt32Value + _python_type = int + + +class BoolValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.BoolValue + _python_type = bool + + +class StringValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.StringValue + _python_type = str + + +class BytesValueMarshal(WrapperMarshal): + _proto_type = wrappers_pb2.BytesValue + _python_type = bytes diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py new file mode 100644 index 000000000000..0f46b3bd9990 --- /dev/null +++ b/packages/proto-plus/proto/message.py @@ -0,0 +1,637 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections +import collections.abc +import copy +import inspect +import re +import uuid +from typing import List, Type + +from google.protobuf import descriptor_pb2 +from google.protobuf import descriptor_pool +from google.protobuf import message +from google.protobuf import reflection +from google.protobuf import symbol_database + +from proto.fields import Field +from proto.fields import MapField +from proto.fields import RepeatedField +from proto.marshal.marshal import marshal +from proto.marshal.types.message import MessageMarshal +from proto.primitives import ProtoType + + +class MessageMeta(type): + """A metaclass for building and registering Message subclasses.""" + + def __new__(mcls, name, bases, attrs): + # Do not do any special behavior for Message itself. + if not bases: + return super().__new__(mcls, name, bases, attrs) + + # Pop metadata off the attrs. + Meta = attrs.pop('Meta', object()) + + # A package and full name should be present. + package = getattr(Meta, 'package', '') + local_path = tuple(attrs.get('__qualname__', name).split('.')) + + # Sanity check: We get the wrong full name if a class is declared + # inside a function local scope; correct this. + if '' in local_path: + ix = local_path.index('') + local_path = local_path[:ix - 1] + local_path[ix + 1:] + + # Determine the full name in protocol buffers. + full_name = getattr(Meta, 'full_name', + '.'.join((package,) + local_path).lstrip('.'), + ) + + # Special case: Maps. Map fields are special; they are essentially + # shorthand for a nested message and a repeated field of that message. + # Decompose each map into its constituent form. + # https://developers.google.com/protocol-buffers/docs/proto3#maps + for key, field in copy.copy(attrs).items(): + if not isinstance(field, MapField): + continue + + # Determine the name of the entry message. + msg_name = '{pascal_key}Entry'.format( + pascal_key=re.sub( + r'_\w', + lambda m: m.group()[1:].upper(), + key, + ).replace(key[0], key[0].upper(), 1), + ) + + # Create the "entry" message (with the key and value fields). + # + # Note: We instantiate an ordered dictionary here and then + # attach key and value in order to ensure that the fields are + # iterated in the correct order when the class is created. + # This is only an issue in Python 3.5, where the order is + # random (and the wrong order causes the pool to refuse to add + # the descriptor because reasons). + entry_attrs = collections.OrderedDict({ + '__module__': attrs.get('__module__', None), + '__qualname__': '{prefix}.{name}'.format( + prefix=attrs.get('__qualname__', name), + name=msg_name, + ), + 'Meta': type('Meta', (object,), { + 'options': descriptor_pb2.MessageOptions(map_entry=True), + 'package': package, + }), + }) + entry_attrs['key'] = Field(field.map_key_type, number=1) + entry_attrs['value'] = Field(field.proto_type, number=2, + enum=field.enum, + message=field.message, + ) + attrs[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) + + # Create the repeated field for the entry message. + attrs[key] = RepeatedField(ProtoType.MESSAGE, + number=field.number, + message=attrs[msg_name], + ) + + # Okay, now we deal with all the rest of the fields. + # Iterate over all the attributes and separate the fields into + # their own sequence. + fields = [] + oneofs = collections.OrderedDict() + proto_imports = set() + index = 0 + for key, field in copy.copy(attrs).items(): + # Sanity check: If this is not a field, do nothing. + if not isinstance(field, Field): + continue + + # Remove the field from the attrs dictionary; the field objects + # themselves should not be direct attributes. + attrs.pop(key) + + # Add data that the field requires that we do not take in the + # constructor because we can derive it from the metaclass. + # (The goal is to make the declaration syntax as nice as possible.) + field.mcls_data = { + 'name': key, + 'parent_name': full_name, + 'index': index, + 'package': package, + } + + # Add the field to the list of fields. + fields.append(field) + + # If this field is part of a "oneof", ensure the oneof itself + # is represented. + if field.oneof: + # Keep a running tally of the index of each oneof, and assign + # that index to the field's descriptor. + oneofs.setdefault(field.oneof, len(oneofs)) + field.descriptor.oneof_index = oneofs[field.oneof] + + # If this field references a message, it may be from another + # proto file; ensure we know about the import (to faithfully + # construct our file descriptor proto). + if field.message and not isinstance(field.message, str): + field_msg = field.message + if hasattr(field_msg, 'pb') and callable(field_msg.pb): + field_msg = field_msg.pb() + + # Sanity check: The field's message may not yet be defined if + # it was a Message defined in the same file, and the file + # descriptor proto has not yet been generated. + # + # We do nothing in this situation; everything will be handled + # correctly when the file descriptor is created later. + if field_msg: + proto_imports.add(field_msg.DESCRIPTOR.file.name) + symbol_database.Default().RegisterMessage(field_msg) + + # Increment the field index counter. + index += 1 + + # Determine the filename. + # We determine an appropriate proto filename based on the + # Python module. If the filename has already been used (which would + # cause collisions in the descriptor pool), we salt it. + filename = '{0}.proto'.format( + attrs.get('__module__', name.lower()).replace('.', '/') + ) + + # Get or create the information about the file, including the + # descriptor to which the new message descriptor shall be added. + file_info = _FileInfo.registry.setdefault(filename, _FileInfo( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, + package=package, + syntax='proto3', + ), + messages=collections.OrderedDict(), + name=filename, + nested={}, + )) + + # Ensure any imports that would be necessary are assigned to the file + # descriptor proto being created. + for proto_import in proto_imports: + if proto_import not in file_info.descriptor.dependency: + file_info.descriptor.dependency.append(proto_import) + + # Retrieve any message options. + opts = getattr(Meta, 'options', descriptor_pb2.MessageOptions()) + + # Create the underlying proto descriptor. + desc = descriptor_pb2.DescriptorProto( + name=name, + field=[i.descriptor for i in fields], + oneof_decl=[descriptor_pb2.OneofDescriptorProto(name=i) + for i in oneofs.keys()], + options=opts, + ) + + # If any descriptors were nested under this one, they need to be + # attached as nested types here. + for child_path in copy.copy(file_info.nested).keys(): + if local_path == child_path[:-1]: + desc.nested_type.add().MergeFrom( + file_info.nested.pop(child_path), + ) + + # Add the descriptor to the file if it is a top-level descriptor, + # or to a "holding area" for nested messages otherwise. + if len(local_path) == 1: + file_info.descriptor.message_type.add().MergeFrom(desc) + else: + file_info.nested[tuple(full_name.split('.'))] = desc + + # Create the MessageInfo instance to be attached to this message. + attrs['_meta'] = _MessageInfo( + fields=fields, + full_name=full_name, + options=opts, + package=package, + ) + + # Run the superclass constructor. + cls = super().__new__(mcls, name, bases, attrs) + + # The info class and fields need a reference to the class just created. + cls._meta.parent = cls + for field in cls._meta.fields.values(): + field.parent = cls + + # Add this message to the _FileInfo instance; this allows us to + # associate the descriptor with the message once the descriptor + # is generated. + file_info.messages[full_name] = cls + + # Generate the descriptor for the file if it is ready. + if file_info.ready(new_class=cls): + file_info.generate_file_pb() + + # Done; return the class. + return cls + + @classmethod + def __prepare__(mcls, name, bases, **kwargs): + return collections.OrderedDict() + + @property + def meta(cls): + return cls._meta + + def pb(cls, obj=None): + """Return the underlying protobuf Message class or instance. + + Args: + obj: If provided, and an instance of ``cls``, return the + underlying protobuf instance. + """ + if obj is None: + return cls.meta.pb + if not isinstance(obj, cls): + raise TypeError('%r is not an instance of %s' % ( + obj, cls.__name__, + )) + return obj._pb + + def wrap(cls, pb): + """Return a Message object that shallowly wraps the descriptor. + + Args: + pb: A protocol buffer object, such as would be returned by + :meth:`pb`. + """ + return cls(pb, __wrap_original=True) + + def serialize(cls, instance) -> bytes: + """Return the serialized proto. + + Args: + instance: An instance of this message type. + + Returns: + bytes: The serialized representation of the protocol buffer. + """ + return cls.pb(instance).SerializeToString() + + def deserialize(cls, payload: bytes) -> 'Message': + """Given a serialized proto, deserialize it into a Message instance. + + Args: + payload (bytes): The serialized proto. + + Returns: + ~.Message: An instance of the message class against which this + method was called. + """ + return cls(cls.pb().FromString(payload)) + + +class Message(metaclass=MessageMeta): + """The abstract base class for a message. + + Args: + mapping (Union[dict, ~.Message]): A dictionary or message to be + used to determine the values for this message. + kwargs (dict): Keys and values corresponding to the fields of the + message. + """ + def __init__(self, mapping=None, **kwargs): + # We accept several things for `mapping`: + # * An instance of this class. + # * An instance of the underlying protobuf descriptor class. + # * A dict + # * Nothing (keyword arguments only). + # + # Sanity check: Did we get something not on that list? Error if so. + if mapping and not isinstance( + mapping, (collections.abc.Mapping, type(self), self._meta.pb)): + raise TypeError('Invalid constructor input for %s: %r' % ( + self.__class__.__name__, mapping, + )) + + # Handle the first two cases: they both involve keeping + # a copy of the underlying protobuf descriptor instance. + if isinstance(mapping, type(self)): + mapping = mapping._pb + if isinstance(mapping, self._meta.pb): + # Make a copy of the mapping. + # This is a constructor for a new object, so users will assume + # that it will not have side effects on the arguments being + # passed in. + # + # The `__wrap_original` argument is private API to override + # this behavior, because `MessageMarshal` actually does want to + # wrap the original argument it was given. The `wrap` method + # on the metaclass is the public API for this behavior. + if not kwargs.pop('__wrap_original', False): + mapping = copy.copy(mapping) + self._pb = mapping + if kwargs: + self._pb.MergeFrom(self._meta.pb(**kwargs)) + return + + # Handle the remaining case by converging the mapping and kwargs + # dictionaries (with kwargs winning), and saving a descriptor + # based on that. + if mapping is None: + mapping = {} + mapping.update(kwargs) + + # Update the mapping to address any values that need to be + # coerced. + for key, value in copy.copy(mapping).items(): + pb_type = self._meta.fields[key].pb_type + pb_value = marshal.to_proto(pb_type, value) + if pb_value is None: + mapping.pop(key) + else: + mapping[key] = pb_value + + # Create the internal protocol buffer. + self._pb = self._meta.pb(**mapping) + + def __bool__(self): + """Return True if any field is truthy, False otherwise.""" + return any([k in self and getattr(self, k) + for k in self._meta.fields.keys()]) + + def __contains__(self, key): + """Return True if this field was set to something non-zero on the wire. + + In most cases, this method will return True when ``__getattr__`` + would return a truthy value and False when it would return a falsy + value, so explicitly calling this is not useful. + + The exception case is empty messages explicitly set on the wire, + which are falsy from ``__getattr__``. This method allows to + distinguish between an explicitly provided empty message and the + absence of that message, which is useful in some edge cases. + + The most common edge case is the use of ``google.protobuf.BoolValue`` + to get a boolean that distinguishes between ``False`` and ``None`` + (or the same for a string, int, etc.). This library transparently + handles that case for you, but this method remains available to + accomodate cases not automatically covered. + + Args: + key (str): The name of the field. + + Returns: + bool: Whether the field's value corresponds to a non-empty + wire serialization. + """ + pb_value = getattr(self._pb, key) + try: + # Protocol buffers "HasField" is unfriendly; it only works + # against composite, non-repeated fields, and raises ValueError + # against any repeated field or primitive. + # + # There is no good way to test whether it is valid to provide + # a field to this method, so sadly we are stuck with a + # somewhat inefficient try/except. + return self._pb.HasField(key) + except ValueError: + return bool(pb_value) + + def __delattr__(self, key): + """Delete the value on the given field. + + This is generally equivalent to setting a falsy value. + """ + self._pb.ClearField(key) + + def __eq__(self, other): + """Return True if the messages are equal, False otherwise.""" + # If these are the same type, use internal protobuf's equality check. + if isinstance(other, type(self)): + return self._pb == other._pb + + # If the other type is the target protobuf object, honor that also. + if isinstance(other, self._meta.pb): + return self._pb == other + + # Ask the other object. + return NotImplemented + + def __getattr__(self, key): + """Retrieve the given field's value. + + In protocol buffers, the presence of a field on a message is + sufficient for it to always be "present". + + For primitives, a value of the correct type will always be returned + (the "falsy" values in protocol buffers consistently match those + in Python). For repeated fields, the falsy value is always an empty + sequence. + + For messages, protocol buffers does distinguish between an empty + message and absence, but this distinction is subtle and rarely + relevant. Therefore, this method always returns an empty message + (following the official implementation). To check for message + presence, use ``key in self`` (in other words, ``__contains__``). + + .. note:: + + Some well-known protocol buffer types + (e.g. ``google.protobuf.Timestamp``) will be converted to + their Python equivalents. See the ``marshal`` module for + more details. + """ + pb_type = self._meta.fields[key].pb_type + pb_value = getattr(self._pb, key) + return marshal.to_python(pb_type, pb_value, absent=key not in self) + + def __ne__(self, other): + """Return True if the messages are unequal, False otherwise.""" + return not self == other + + def __repr__(self): + return repr(self._pb) + + def __setattr__(self, key, value): + """Set the value on the given field. + + For well-known protocol buffer types which are marshalled, either + the protocol buffer object or the Python equivalent is accepted. + """ + if key.startswith('_'): + return super().__setattr__(key, value) + pb_type = self._meta.fields[key].pb_type + pb_value = marshal.to_proto(pb_type, value) + + # We *always* clear the existing field. + # This is the only way to successfully write nested falsy values, + # because otherwise MergeFrom will no-op on them. + self._pb.ClearField(key) + + # Merge in the value being set. + if pb_value is not None: + self._pb.MergeFrom(self._meta.pb(**{key: pb_value})) + + +class _MessageInfo: + """Metadata about a message. + + Args: + fields (Tuple[~.fields.Field]): The fields declared on the message. + package (str): The proto package. + full_name (str): The full name of the message. + file_info (~._FileInfo): The file descriptor and messages for the + file containing this message. + options (~.descriptor_pb2.MessageOptions): Any options that were + set on the message. + """ + def __init__(self, *, fields: List[Field], package: str, full_name: str, + options: descriptor_pb2.MessageOptions) -> None: + self.package = package + self.full_name = full_name + self.options = options + self.fields = collections.OrderedDict([ + (i.name, i) for i in fields + ]) + self.fields_by_number = collections.OrderedDict([ + (i.number, i) for i in fields + ]) + self._pb = None + + @property + def pb(self) -> Type[message.Message]: + """Return the protobuf message type for this descriptor. + + If a field on the message references another message which has not + loaded, then this method returns None. + """ + return self._pb + + +class _FileInfo(collections.namedtuple( + '_FileInfo', ['descriptor', 'messages', 'name', 'nested'])): + registry = {} # Mapping[str, '_FileInfo'] + + def generate_file_pb(self): + """Generate the descriptors for all protos in the file. + + This method takes the file descriptor attached to the parent + message and generates the immutable descriptors for all of the + messages in the file descriptor. (This must be done in one fell + swoop for immutability and to resolve proto cross-referencing.) + + This is run automatically when the last proto in the file is + generated, as determined by the module's __all__ tuple. + """ + pool = descriptor_pool.Default() + + # Salt the filename in the descriptor. + # This allows re-use of the filename by other proto messages if + # needed (e.g. if __all__ is not used). + self.descriptor.name = '{prefix}_{salt}.proto'.format( + prefix=self.descriptor.name[:-6], + salt=str(uuid.uuid4())[0:8], + ) + + # Add the file descriptor. + pool.Add(self.descriptor) + + # Adding the file descriptor to the pool created a descriptor for + # each message; go back through our wrapper messages and associate + # them with the internal protobuf version. + for full_name, proto_plus_message in self.messages.items(): + # Get the descriptor from the pool, and create the protobuf + # message based on it. + descriptor = pool.FindMessageTypeByName(full_name) + pb_message = reflection.GeneratedProtocolMessageType( + descriptor.name, + (message.Message,), + {'DESCRIPTOR': descriptor, '__module__': None}, + ) + + # Register the message with the marshal so it is wrapped + # appropriately. + proto_plus_message._meta._pb = pb_message + marshal.register( + pb_message, + MessageMarshal(pb_message, proto_plus_message) + ) + + # Iterate over any fields on the message and, if their type + # is a message still referenced as a string, resolve the reference. + for field in proto_plus_message._meta.fields.values(): + if field.message and isinstance(field.message, str): + field.message = self.messages[field.message] + + # We no longer need to track this file's info; remove it from + # the module's registry and from this object. + self.registry.pop(self.name) + + def ready(self, new_class): + """Return True if a file descriptor may added, False otherwise. + + This determine if all the messages that we plan to create have been + created, as best as we are able. + + Since messages depend on one another, we create descriptor protos + (which reference each other using strings) and wait until we have + built everything that is going to be in the module, and then + use the descriptor protos to instantiate the actual descriptors in + one fell swoop. + + Args: + new_class (~.MessageMeta): The new class currently undergoing + creation. + """ + # If there are any nested descriptors that have not been assigned to + # the descriptors that should contain them, then we are not ready. + if len(self.nested): + return False + + # If there are any unresolved fields (fields with a composite message + # declared as a string), ensure that the corresponding message is + # declared. + for field in self.unresolved_fields: + if field.message not in self.messages: + return False + + # If the module in which this class is defined provides an __all__, + # do not generate the file descriptor until every member of __all__ + # has been populated. + module = inspect.getmodule(new_class) + manifest = set(getattr(module, '__all__', ())).difference( + {new_class.__name__}, + ) + if not all([hasattr(module, i) for i in manifest]): + return False + + # Okay, we are ready. + return True + + @property + def unresolved_fields(self): + """Return fields with referencing message types as strings.""" + for proto_plus_message in self.messages.values(): + for field in proto_plus_message._meta.fields.values(): + if field.message and isinstance(field.message, str): + yield field + + +__all__ = ( + 'Message', +) diff --git a/packages/proto-plus/proto/primitives.py b/packages/proto-plus/proto/primitives.py new file mode 100644 index 000000000000..683aaa20d280 --- /dev/null +++ b/packages/proto-plus/proto/primitives.py @@ -0,0 +1,37 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import enum + + +class ProtoType(enum.IntEnum): + """The set of basic types in protocol buffers.""" + # These values come from google/protobuf/descriptor.proto + DOUBLE = 1 + FLOAT = 2 + INT64 = 3 + UINT64 = 4 + INT32 = 5 + FIXED64 = 6 + FIXED32 = 7 + BOOL = 8 + STRING = 9 + MESSAGE = 11 + BYTES = 12 + UINT32 = 13 + ENUM = 14 + SFIXED32 = 15 + SFIXED64 = 16 + SINT32 = 17 + SINT64 = 18 diff --git a/packages/proto-plus/proto/utils.py b/packages/proto-plus/proto/utils.py new file mode 100644 index 000000000000..90172947bd42 --- /dev/null +++ b/packages/proto-plus/proto/utils.py @@ -0,0 +1,48 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools + + +def cached_property(fx): + """Make the callable into a cached property. + + Similar to @property, but the function will only be called once per + object. + + Args: + fx (Callable[]): The property function. + + Returns: + Callable[]: The wrapped function. + """ + @functools.wraps(fx) + def inner(self): + # Sanity check: If there is no cache at all, create an empty cache. + if not hasattr(self, '_cached_values'): + object.__setattr__(self, '_cached_values', {}) + + # If and only if the function's result is not in the cache, + # run the function. + if fx.__name__ not in self._cached_values: + self._cached_values[fx.__name__] = fx(self) + + # Return the value from cache. + return self._cached_values[fx.__name__] + return property(inner) + + +__all__ = ( + 'cached_property', +) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py new file mode 100644 index 000000000000..b3db993dfe3f --- /dev/null +++ b/packages/proto-plus/setup.py @@ -0,0 +1,54 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +import os + +from setuptools import find_packages, setup + + +PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) + +with io.open(os.path.join(PACKAGE_ROOT, 'README.rst')) as file_obj: + README = file_obj.read() + +setup( + name='proto-plus', + version='0.1.0a6', + license='Apache 2.0', + author='Luke Sneeringer', + author_email='lukesneeringer@google.com', + url='https://github.com/googleapis/proto-plus-python.git', + packages=find_packages(exclude=['docs', 'tests']), + description='Beautiful, Pythonic protocol buffers.', + long_description=README, + platforms='Posix; MacOS X', + include_package_data=True, + install_requires=( + 'protobuf >= 3.5.1', + ), + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Topic :: Software Development :: Code Generators', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + zip_safe=False, +) diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py new file mode 100644 index 000000000000..f4bf1084ff48 --- /dev/null +++ b/packages/proto-plus/tests/conftest.py @@ -0,0 +1,83 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import imp +from unittest import mock + +from google.protobuf import descriptor_pool +from google.protobuf import message +from google.protobuf import reflection +from google.protobuf import symbol_database + +import proto +from proto.marshal import types +from proto.message import _FileInfo + + +def pytest_runtest_setup(item): + _FileInfo.registry.clear() + + # Replace the descriptor pool and symbol database to avoid tests + # polluting one another. + pool = descriptor_pool.DescriptorPool() + sym_db = symbol_database.SymbolDatabase(pool=pool) + item._mocks = ( + mock.patch.object(descriptor_pool, 'Default', return_value=pool), + mock.patch.object(symbol_database, 'Default', return_value=sym_db), + ) + [i.start() for i in item._mocks] + + # Importing a pb2 module registers those messages with the pool. + # However, our test harness is subbing out the default pool above, + # which means that all the dependencies that messages may depend on + # are now absent from the pool. + # + # Add any pb2 modules that may have been imported by the test's module to + # the descriptor pool and symbol database. + # + # This is exceptionally tricky in the C implementation because there is + # no way to add an existing descriptor to a pool; the only acceptable + # approach is to add a file descriptor proto, which then creates *new* + # descriptors. We therefore do that and then plop the replacement classes + # onto the pb2 modules. + reloaded = set() + for name in dir(item.module): + if name.endswith('_pb2') and not name.startswith('test_'): + module = getattr(item.module, name) + pool.AddSerializedFile(module.DESCRIPTOR.serialized_pb) + fd = pool.FindFileByName(module.DESCRIPTOR.name) + for message_name, descriptor in fd.message_types_by_name.items(): + new_message = reflection.GeneratedProtocolMessageType( + message_name, + (message.Message,), + {'DESCRIPTOR': descriptor, '__module__': None}, + ) + sym_db.RegisterMessage(new_message) + setattr(module, message_name, new_message) + + # Track which modules had new message classes loaded. + # This is used below to wire the new classes into the marshal. + reloaded.add(name) + + # If the marshal had previously registered the old message classes, + # then reload the appropriate modules so the marshal is using the new ones. + if 'wrappers_pb2' in reloaded: + imp.reload(types.wrappers) + if reloaded.intersection({'timestamp_pb2', 'duration_pb2'}): + imp.reload(types.dates) + proto.marshal.reset() + + +def pytest_runtest_teardown(item): + [i.stop() for i in item._mocks] diff --git a/packages/proto-plus/tests/test_fields_bytes.py b/packages/proto-plus/tests/test_fields_bytes.py new file mode 100644 index 000000000000..eba15daed8dd --- /dev/null +++ b/packages/proto-plus/tests/test_fields_bytes.py @@ -0,0 +1,75 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import proto + + +def test_bytes_init(): + class Foo(proto.Message): + bar = proto.Field(proto.BYTES, number=1) + baz = proto.Field(proto.BYTES, number=2) + + foo = Foo(bar=b'spam') + assert foo.bar == b'spam' + assert foo.baz == b'' + assert not foo.baz + assert Foo.pb(foo).bar == b'spam' + assert Foo.pb(foo).baz == b'' + + +def test_bytes_rmw(): + class Foo(proto.Message): + spam = proto.Field(proto.BYTES, number=1) + eggs = proto.Field(proto.BYTES, number=2) + + foo = Foo(spam=b'bar') + foo.eggs = b'baz' + assert foo.spam == b'bar' + assert foo.eggs == b'baz' + assert Foo.pb(foo).spam == b'bar' + assert Foo.pb(foo).eggs == b'baz' + foo.spam = b'bacon' + assert foo.spam == b'bacon' + assert foo.eggs == b'baz' + assert Foo.pb(foo).spam == b'bacon' + assert Foo.pb(foo).eggs == b'baz' + + +def test_bytes_del(): + class Foo(proto.Message): + bar = proto.Field(proto.BYTES, number=1) + + foo = Foo(bar=b'spam') + assert foo.bar == b'spam' + del foo.bar + assert foo.bar == b'' + assert not foo.bar + + +def test_bytes_string_distinct(): + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.BYTES, number=2) + + foo = Foo() + assert foo.bar != foo.baz + + # Since protobuf was written against Python 2, it accepts bytes objects + # for strings (but not vice versa). + foo.bar = b'anything' + assert foo.bar == 'anything' + with pytest.raises(TypeError): + foo.baz = 'anything' diff --git a/packages/proto-plus/tests/test_fields_composite.py b/packages/proto-plus/tests/test_fields_composite.py new file mode 100644 index 000000000000..fbcaa594df26 --- /dev/null +++ b/packages/proto-plus/tests/test_fields_composite.py @@ -0,0 +1,93 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +def test_composite_init(): + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.INT64, number=2) + + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + eggs = proto.Field(proto.BOOL, number=2) + + spam = Spam(foo=Foo(bar='str', baz=42)) + assert spam.foo.bar == 'str' + assert spam.foo.baz == 42 + assert spam.eggs is False + + +def test_composite_inner_rmw(): + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + + spam = Spam(foo=Foo(bar='str')) + spam.foo.bar = 'other str' + assert spam.foo.bar == 'other str' + assert Spam.pb(spam).foo.bar == 'other str' + + +def test_composite_empty_inner_rmw(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + + spam = Spam() + spam.foo.bar = 42 + assert spam.foo.bar == 42 + + +def test_composite_outer_rmw(): + class Foo(proto.Message): + bar = proto.Field(proto.FLOAT, number=1) + + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + + spam = Spam(foo=Foo(bar=3.14159)) + spam.foo = Foo(bar=2.71828) + assert abs(spam.foo.bar - 2.71828) < 1e-7 + + +def test_composite_dict_write(): + class Foo(proto.Message): + bar = proto.Field(proto.FLOAT, number=1) + + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + + spam = Spam() + spam.foo = {'bar': 2.71828} + assert abs(spam.foo.bar - 2.71828) < 1e-7 + + +def test_composite_del(): + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + + spam = Spam(foo=Foo(bar='str')) + del spam.foo + assert not spam.foo + assert isinstance(spam.foo, Foo) + assert spam.foo.bar == '' diff --git a/packages/proto-plus/tests/test_fields_composite_string_ref.py b/packages/proto-plus/tests/test_fields_composite_string_ref.py new file mode 100644 index 000000000000..865bc0da5f8a --- /dev/null +++ b/packages/proto-plus/tests/test_fields_composite_string_ref.py @@ -0,0 +1,107 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +def test_composite_forward_ref(): + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message='Foo') + eggs = proto.Field(proto.BOOL, number=2) + + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.INT64, number=2) + + spam = Spam(foo=Foo(bar='str', baz=42)) + assert spam.foo.bar == 'str' + assert spam.foo.baz == 42 + assert spam.eggs is False + + +def test_composite_forward_ref_with_package(): + class Spam(proto.Message): + foo = proto.Field('Foo', number=1) + + class Meta: + package = 'abc.def' + + class Eggs(proto.Message): + foo = proto.Field('abc.def.Foo', number=1) + + class Meta: + package = 'abc.def' + + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.INT64, number=2) + + class Meta: + package = 'abc.def' + + spam = Spam(foo=Foo(bar='str', baz=42)) + eggs = Eggs(foo=Foo(bar='rts', baz=24)) + assert spam.foo.bar == 'str' + assert spam.foo.baz == 42 + assert eggs.foo.bar == 'rts' + assert eggs.foo.baz == 24 + + +def test_composite_backward_ref(): + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.INT64, number=2) + + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + eggs = proto.Field(proto.BOOL, number=2) + + spam = Spam(foo=Foo(bar='str', baz=42)) + assert spam.foo.bar == 'str' + assert spam.foo.baz == 42 + assert spam.eggs is False + + +def test_composite_multi_ref(): + class Spam(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message='Foo') + eggs = proto.Field(proto.BOOL, number=2) + + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.INT64, number=2) + + class Bacon(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + + spam = Spam(foo=Foo(bar='str', baz=42)) + bacon = Bacon(foo=spam.foo) + assert spam.foo.bar == 'str' + assert spam.foo.baz == 42 + assert spam.eggs is False + assert bacon.foo == spam.foo + + +def test_composite_self_ref(): + class Spam(proto.Message): + spam = proto.Field(proto.MESSAGE, number=1, message='Spam') + eggs = proto.Field(proto.BOOL, number=2) + + spam = Spam(spam=Spam(eggs=True)) + assert spam.eggs is False + assert spam.spam.eggs is True + assert type(spam) is type(spam.spam) # noqa: E0721 + assert not spam.spam.spam + assert spam.spam.spam.eggs is False + assert not spam.spam.spam.spam.spam.spam.spam.spam diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py new file mode 100644 index 000000000000..a3490147244a --- /dev/null +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -0,0 +1,194 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import enum + +import proto + + +def test_outer_enum_init(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo(color=Color.RED) + assert foo.color == Color.RED + assert foo.color == 1 + assert foo.color + assert Foo.pb(foo).color == 1 + + +def test_outer_enum_init_int(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo(color=1) + assert foo.color == Color.RED + assert foo.color == 1 + assert foo.color + assert Foo.pb(foo).color == 1 + + +def test_outer_enum_init_dict(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo({'color': 1}) + assert foo.color == Color.RED + assert foo.color == 1 + assert foo.color + assert Foo.pb(foo).color == 1 + + +def test_outer_enum_init_pb2(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo(Foo.pb()(color=Color.RED)) + assert foo.color == Color.RED + assert foo.color == 1 + assert foo.color + assert Foo.pb(foo).color == 1 + + +def test_outer_enum_unset(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo() + assert foo.color == Color.COLOR_UNSPECIFIED + assert foo.color == 0 + assert 'color' not in foo + assert not foo.color + assert Foo.pb(foo).color == 0 + assert Foo.serialize(foo) == b'' + + +def test_outer_enum_write(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo() + foo.color = Color.GREEN + assert foo.color == Color.GREEN + assert foo.color == 2 + assert Foo.pb(foo).color == 2 + assert foo.color + + +def test_outer_enum_write_int(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo() + foo.color = 3 + assert foo.color == Color.BLUE + assert foo.color == 3 + assert Foo.pb(foo).color == 3 + assert foo.color + + +def test_inner_enum_init(): + class Foo(proto.Message): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo(color=Foo.Color.RED) + assert foo.color == Foo.Color.RED + assert foo.color == 1 + assert foo.color + assert Foo.pb(foo).color == 1 + + +def test_inner_enum_write(): + class Foo(proto.Message): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo() + foo.color = Foo.Color.GREEN + assert foo.color == Foo.Color.GREEN + assert foo.color == 2 + assert Foo.pb(foo).color == 2 + assert foo.color + + +def test_enum_del(): + class Color(enum.IntEnum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + + foo = Foo(color=Color.BLUE) + del foo.color + assert foo.color == Color.COLOR_UNSPECIFIED + assert foo.color == 0 + assert 'color' not in foo + assert not foo.color + assert Foo.pb(foo).color == 0 diff --git a/packages/proto-plus/tests/test_fields_int.py b/packages/proto-plus/tests/test_fields_int.py new file mode 100644 index 000000000000..03614388e8b8 --- /dev/null +++ b/packages/proto-plus/tests/test_fields_int.py @@ -0,0 +1,95 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import proto + + +def test_int_init(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + baz = proto.Field(proto.INT32, number=2) + + foo = Foo(bar=42) + assert foo.bar == 42 + assert foo.baz == 0 + assert not foo.baz + assert Foo.pb(foo).bar == 42 + assert Foo.pb(foo).baz == 0 + + +def test_int_rmw(): + class Foo(proto.Message): + spam = proto.Field(proto.INT32, number=1) + eggs = proto.Field(proto.INT32, number=2) + + foo = Foo(spam=42) + foo.eggs = 76 # trombones led the big parade... + assert foo.spam == 42 + assert foo.eggs == 76 + assert Foo.pb(foo).spam == 42 + assert Foo.pb(foo).eggs == 76 + foo.spam = 144 + assert foo.spam == 144 + assert foo.eggs == 76 + assert Foo.pb(foo).spam == 144 + assert Foo.pb(foo).eggs == 76 + + +def test_int_del(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + foo = Foo(bar=42) + assert foo.bar == 42 + del foo.bar + assert foo.bar == 0 + assert not foo.bar + + +def test_int_size(): + class Foo(proto.Message): + small = proto.Field(proto.INT32, number=1) + big = proto.Field(proto.INT64, number=2) + + foo = Foo() + foo.big = 2 ** 40 + assert foo.big == 2 ** 40 + with pytest.raises(ValueError): + foo.small = 2 ** 40 + with pytest.raises(ValueError): + Foo(small=2 ** 40) + + +def test_int_unsigned(): + class Foo(proto.Message): + signed = proto.Field(proto.INT32, number=1) + unsigned = proto.Field(proto.UINT32, number=2) + + foo = Foo() + foo.signed = -10 + assert foo.signed == -10 + with pytest.raises(ValueError): + foo.unsigned = -10 + with pytest.raises(ValueError): + Foo(unsigned=-10) + + +def test_field_descriptor_idempotent(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + bar_field = Foo.meta.fields['bar'] + assert bar_field.descriptor is bar_field.descriptor diff --git a/packages/proto-plus/tests/test_fields_map_composite.py b/packages/proto-plus/tests/test_fields_map_composite.py new file mode 100644 index 000000000000..696fa1c78e28 --- /dev/null +++ b/packages/proto-plus/tests/test_fields_map_composite.py @@ -0,0 +1,102 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import proto + + +def test_composite_map(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, + message=Foo, + ) + + baz = Baz(foos={'i': Foo(bar=42), 'j': Foo(bar=24)}) + assert len(baz.foos) == 2 + assert baz.foos['i'].bar == 42 + assert baz.foos['j'].bar == 24 + assert 'k' not in baz.foos + + +def test_composite_map_dict(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, + message=Foo, + ) + + baz = Baz(foos={'i': {'bar': 42}, 'j': {'bar': 24}}) + assert len(baz.foos) == 2 + assert baz.foos['i'].bar == 42 + assert baz.foos['j'].bar == 24 + assert 'k' not in baz.foos + with pytest.raises(KeyError): + baz.foos['k'] + + +def test_composite_map_set(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, + message=Foo, + ) + + baz = Baz() + baz.foos['i'] = Foo(bar=42) + baz.foos['j'] = Foo(bar=24) + assert len(baz.foos) == 2 + assert baz.foos['i'].bar == 42 + assert baz.foos['j'].bar == 24 + assert 'k' not in baz.foos + + +def test_composite_map_deep_set(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, + message=Foo, + ) + + baz = Baz() + baz.foos['i'] = Foo() + baz.foos['i'].bar = 42 + assert len(baz.foos) == 1 + assert baz.foos['i'].bar == 42 + + +def test_composite_map_del(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, + message=Foo, + ) + + baz = Baz() + baz.foos['i'] = Foo(bar=42) + assert len(baz.foos) == 1 + del baz.foos['i'] + assert len(baz.foos) == 0 + assert 'i' not in baz.foos diff --git a/packages/proto-plus/tests/test_fields_map_scalar.py b/packages/proto-plus/tests/test_fields_map_scalar.py new file mode 100644 index 000000000000..27eeed74ae08 --- /dev/null +++ b/packages/proto-plus/tests/test_fields_map_scalar.py @@ -0,0 +1,58 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +def test_basic_map(): + class Foo(proto.Message): + tags = proto.MapField(proto.STRING, proto.STRING, number=1) + + foo = Foo(tags={'a': 'foo', 'b': 'bar'}) + assert foo.tags['a'] == 'foo' + assert foo.tags['b'] == 'bar' + assert 'c' not in foo.tags + + +def test_basic_map_with_underscore_field_name(): + class Foo(proto.Message): + tag_labels = proto.MapField(proto.STRING, proto.STRING, number=1) + + foo = Foo(tag_labels={'a': 'foo', 'b': 'bar'}) + assert foo.tag_labels['a'] == 'foo' + assert foo.tag_labels['b'] == 'bar' + assert 'c' not in foo.tag_labels + + +def test_basic_map_assignment(): + class Foo(proto.Message): + tags = proto.MapField(proto.STRING, proto.STRING, number=1) + + foo = Foo(tags={'a': 'foo'}) + foo.tags['b'] = 'bar' + assert len(foo.tags) == 2 + assert foo.tags['a'] == 'foo' + assert foo.tags['b'] == 'bar' + assert 'c' not in foo.tags + + +def test_basic_map_deletion(): + class Foo(proto.Message): + tags = proto.MapField(proto.STRING, proto.STRING, number=1) + + foo = Foo(tags={'a': 'foo', 'b': 'bar'}) + del foo.tags['b'] + assert len(foo.tags) == 1 + assert foo.tags['a'] == 'foo' + assert 'b' not in foo.tags diff --git a/packages/proto-plus/tests/test_fields_oneof.py b/packages/proto-plus/tests/test_fields_oneof.py new file mode 100644 index 000000000000..8f0d12446b9a --- /dev/null +++ b/packages/proto-plus/tests/test_fields_oneof.py @@ -0,0 +1,49 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +def test_oneof(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1, oneof='bacon') + baz = proto.Field(proto.STRING, number=2, oneof='bacon') + + foo = Foo(bar=42) + assert foo.bar == 42 + assert not foo.baz + foo.baz = 'the answer' + assert not foo.bar + assert foo.baz == 'the answer' + + +def test_multiple_oneofs(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1, oneof='spam') + baz = proto.Field(proto.STRING, number=2, oneof='spam') + bacon = proto.Field(proto.FLOAT, number=3, oneof='eggs') + ham = proto.Field(proto.STRING, number=4, oneof='eggs') + + foo = Foo() + foo.bar = 42 + foo.bacon = 42.0 + assert foo.bar == 42 + assert foo.bacon == 42.0 + assert not foo.baz + assert not foo.ham + foo.ham = 'this one gets assigned' + assert not foo.bacon + assert foo.ham == 'this one gets assigned' + assert foo.bar == 42 + assert not foo.baz diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py new file mode 100644 index 000000000000..d9e0b4d9827f --- /dev/null +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -0,0 +1,168 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from datetime import datetime +from datetime import timezone + +import pytest + +from google.protobuf import timestamp_pb2 + +import proto + + +def test_repeated_composite_init(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[Foo(bar=42)]) + assert len(baz.foos) == 1 + assert baz.foos[0].bar == 42 + + +def test_repeated_composite_init_struct(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[{'bar': 42}]) + assert len(baz.foos) == 1 + assert baz.foos[0].bar == 42 + + +def test_repeated_composite_falsy_behavior(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz() + assert not baz.foos + assert len(baz.foos) == 0 + + +def test_repeated_composite_marshaled(): + class Foo(proto.Message): + timestamps = proto.RepeatedField(proto.MESSAGE, + message=timestamp_pb2.Timestamp, + number=1, + ) + + foo = Foo(timestamps=[datetime(2012, 4, 21, 15, tzinfo=timezone.utc)]) + foo.timestamps.append(timestamp_pb2.Timestamp(seconds=86400 * 365)) + foo.timestamps.append(datetime(2017, 10, 14, tzinfo=timezone.utc)) + assert all([isinstance(i, datetime) for i in foo.timestamps]) + assert all([isinstance(i, timestamp_pb2.Timestamp) + for i in Foo.pb(foo).timestamps]) + assert foo.timestamps[0].year == 2012 + assert foo.timestamps[0].month == 4 + assert foo.timestamps[0].hour == 15 + assert foo.timestamps[1].year == 1971 + assert foo.timestamps[1].month == 1 + assert foo.timestamps[1].hour == 0 + assert foo.timestamps[2].year == 2017 + assert foo.timestamps[2].month == 10 + assert foo.timestamps[2].hour == 0 + + +def test_repeated_composite_outer_write(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz() + baz.foos = [Foo(bar=96), Foo(bar=48)] + assert len(baz.foos) == 2 + assert baz.foos[0].bar == 96 + assert baz.foos[1].bar == 48 + + +def test_repeated_composite_append(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz() + baz.foos.append(Foo(bar=96)) + baz.foos.append({'bar': 72}) + assert len(baz.foos) == 2 + assert baz.foos[0].bar == 96 + assert baz.foos[1].bar == 72 + + +def test_repeated_composite_insert(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz() + baz.foos.insert(0, {'bar': 72}) + baz.foos.insert(0, Foo(bar=96)) + assert len(baz.foos) == 2 + assert baz.foos[0].bar == 96 + assert baz.foos[1].bar == 72 + + +def test_repeated_composite_iadd(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz() + baz.foos += [Foo(bar=96), Foo(bar=48)] + assert len(baz.foos) == 2 + assert baz.foos[0].bar == 96 + assert baz.foos[1].bar == 48 + + +def test_repeated_composite_set(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[{'bar': 96}, {'bar': 48}]) + baz.foos[1] = Foo(bar=55) + assert baz.foos[0].bar == 96 + assert baz.foos[1].bar == 55 + + +def test_repeated_composite_set_wrong_type(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class NotFoo(proto.Message): + eggs = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz() + with pytest.raises(TypeError): + baz.foos.append(NotFoo(eggs=42)) diff --git a/packages/proto-plus/tests/test_fields_repeated_scalar.py b/packages/proto-plus/tests/test_fields_repeated_scalar.py new file mode 100644 index 000000000000..ea171892c1ab --- /dev/null +++ b/packages/proto-plus/tests/test_fields_repeated_scalar.py @@ -0,0 +1,114 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy + +import pytest + +import proto + + +def test_repeated_scalar_init(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + assert foo.bar == [1, 1, 2, 3, 5, 8, 13] + + +def test_repeated_scalar_append(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + foo.bar.append(21) + foo.bar.append(34) + assert foo.bar == [1, 1, 2, 3, 5, 8, 13, 21, 34] + + +def test_repeated_scalar_iadd(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + foo.bar += [21, 34] + assert foo.bar == [1, 1, 2, 3, 5, 8, 13, 21, 34] + + +def test_repeated_scalar_setitem(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + foo.bar[4] = 99 + assert foo.bar == [1, 1, 2, 3, 99, 8, 13] + assert foo.bar[4] == 99 + + +def test_repeated_scalar_overwrite(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + foo.bar = [1, 2, 4, 8, 16] + assert foo.bar == [1, 2, 4, 8, 16] + + +def test_repeated_scalar_eq_ne(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + assert foo.bar == copy.copy(foo.bar) + assert foo.bar != [1, 2, 4, 8, 16] + + +def test_repeated_scalar_del(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + del foo.bar + assert foo.bar == [] + assert not foo.bar + + +def test_repeated_scalar_delitem(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + del foo.bar[5] + del foo.bar[3] + assert foo.bar == [1, 1, 2, 5, 13] + + +def test_repeated_scalar_sort(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[8, 1, 2, 1, 21, 3, 13, 5]) + foo.bar.sort() + assert foo.bar == [1, 1, 2, 3, 5, 8, 13, 21] + + +def test_repeated_scalar_wrong_type(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT32, number=1) + + foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) + with pytest.raises(TypeError): + foo.bar.append(21.0) + with pytest.raises(TypeError): + foo.bar.append('21') diff --git a/packages/proto-plus/tests/test_fields_string.py b/packages/proto-plus/tests/test_fields_string.py new file mode 100644 index 000000000000..eadbbd286da6 --- /dev/null +++ b/packages/proto-plus/tests/test_fields_string.py @@ -0,0 +1,57 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +def test_string_init(): + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.STRING, number=2) + + foo = Foo(bar='spam') + assert foo.bar == 'spam' + assert foo.baz == '' + assert not foo.baz + assert Foo.pb(foo).bar == 'spam' + assert Foo.pb(foo).baz == '' + + +def test_string_rmw(): + class Foo(proto.Message): + spam = proto.Field(proto.STRING, number=1) + eggs = proto.Field(proto.STRING, number=2) + + foo = Foo(spam='bar') + foo.eggs = 'baz' + assert foo.spam == 'bar' + assert foo.eggs == 'baz' + assert Foo.pb(foo).spam == 'bar' + assert Foo.pb(foo).eggs == 'baz' + foo.spam = 'bacon' + assert foo.spam == 'bacon' + assert foo.eggs == 'baz' + assert Foo.pb(foo).spam == 'bacon' + assert Foo.pb(foo).eggs == 'baz' + + +def test_string_del(): + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + + foo = Foo(bar='spam') + assert foo.bar == 'spam' + del foo.bar + assert foo.bar == '' + assert not foo.bar diff --git a/packages/proto-plus/tests/test_marshal_register.py b/packages/proto-plus/tests/test_marshal_register.py new file mode 100644 index 000000000000..cb36f3ee4b34 --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_register.py @@ -0,0 +1,53 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from google.protobuf import empty_pb2 + +import proto + + +def test_registration(): + @proto.marshal.register(empty_pb2.Empty) + class Marshal: + def to_proto(self, value): + return value + + def to_python(self, value, *, absent=None): + return value + assert isinstance(proto.marshal._registry[empty_pb2.Empty], Marshal) + + +def test_invalid_target_registration(): + with pytest.raises(TypeError): + @proto.marshal.register(object) + class Marshal: + def to_proto(self, value): + return value + + def to_python(self, value, *, absent=None): + return value + + +def test_invalid_marshal_class(): + with pytest.raises(TypeError): + @proto.marshal.register(empty_pb2.Empty) + class Marshal: + pass + + +def test_invalid_marshal_rule(): + with pytest.raises(TypeError): + proto.marshal.register(empty_pb2.Empty, rule=object()) diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py new file mode 100644 index 000000000000..0a35ebf98a20 --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -0,0 +1,228 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from datetime import datetime +from datetime import timedelta +from datetime import timezone + +from google.protobuf import duration_pb2 +from google.protobuf import timestamp_pb2 + +import proto + + +def test_timestamp_read(): + class Foo(proto.Message): + event_time = proto.Field(proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + foo = Foo(event_time=timestamp_pb2.Timestamp(seconds=1335020400)) + assert isinstance(foo.event_time, datetime) + assert foo.event_time.year == 2012 + assert foo.event_time.month == 4 + assert foo.event_time.day == 21 + assert foo.event_time.hour == 15 + assert foo.event_time.minute == 0 + assert foo.event_time.tzinfo == timezone.utc + + +def test_timestamp_write_init(): + class Foo(proto.Message): + event_time = proto.Field(proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + foo = Foo(event_time=datetime(2012, 4, 21, 15, tzinfo=timezone.utc)) + assert isinstance(foo.event_time, datetime) + assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) + assert foo.event_time.year == 2012 + assert foo.event_time.month == 4 + assert foo.event_time.hour == 15 + assert Foo.pb(foo).event_time.seconds == 1335020400 + + +def test_timestamp_write(): + class Foo(proto.Message): + event_time = proto.Field(proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + foo = Foo() + foo.event_time = datetime(2012, 4, 21, 15, tzinfo=timezone.utc) + assert isinstance(foo.event_time, datetime) + assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) + assert foo.event_time.year == 2012 + assert foo.event_time.month == 4 + assert foo.event_time.hour == 15 + assert Foo.pb(foo).event_time.seconds == 1335020400 + + +def test_timestamp_write_pb2(): + class Foo(proto.Message): + event_time = proto.Field(proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + foo = Foo() + foo.event_time = timestamp_pb2.Timestamp(seconds=1335020400) + assert isinstance(foo.event_time, datetime) + assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) + assert foo.event_time.year == 2012 + assert foo.event_time.month == 4 + assert foo.event_time.hour == 15 + assert Foo.pb(foo).event_time.seconds == 1335020400 + + +def test_timestamp_rmw_nanos(): + class Foo(proto.Message): + event_time = proto.Field(proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + foo = Foo() + foo.event_time = datetime(2012, 4, 21, 15, 0, 0, 1, tzinfo=timezone.utc) + assert foo.event_time.microsecond == 1 + assert Foo.pb(foo).event_time.nanos == 1000 + foo.event_time = foo.event_time.replace(microsecond=2) + assert foo.event_time.microsecond == 2 + assert Foo.pb(foo).event_time.nanos == 2000 + + +def test_timestamp_absence(): + class Foo(proto.Message): + event_time = proto.Field(proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + foo = Foo() + assert foo.event_time is None + + +def test_timestamp_del(): + class Foo(proto.Message): + event_time = proto.Field(proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + foo = Foo(event_time=datetime(2012, 4, 21, 15, tzinfo=timezone.utc)) + del foo.event_time + assert foo.event_time is None + + +def test_duration_read(): + class Foo(proto.Message): + ttl = proto.Field(proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + foo = Foo(ttl=duration_pb2.Duration(seconds=60, nanos=1000)) + assert isinstance(foo.ttl, timedelta) + assert isinstance(Foo.pb(foo).ttl, duration_pb2.Duration) + assert foo.ttl.days == 0 + assert foo.ttl.seconds == 60 + assert foo.ttl.microseconds == 1 + + +def test_duration_write_init(): + class Foo(proto.Message): + ttl = proto.Field(proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + foo = Foo(ttl=timedelta(days=2)) + assert isinstance(foo.ttl, timedelta) + assert isinstance(Foo.pb(foo).ttl, duration_pb2.Duration) + assert foo.ttl.days == 2 + assert foo.ttl.seconds == 0 + assert foo.ttl.microseconds == 0 + assert Foo.pb(foo).ttl.seconds == 86400 * 2 + + +def test_duration_write(): + class Foo(proto.Message): + ttl = proto.Field(proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + foo = Foo() + foo.ttl = timedelta(seconds=120) + assert isinstance(foo.ttl, timedelta) + assert isinstance(Foo.pb(foo).ttl, duration_pb2.Duration) + assert foo.ttl.seconds == 120 + assert Foo.pb(foo).ttl.seconds == 120 + + +def test_duration_write_pb2(): + class Foo(proto.Message): + ttl = proto.Field(proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + foo = Foo() + foo.ttl = duration_pb2.Duration(seconds=120) + assert isinstance(foo.ttl, timedelta) + assert isinstance(Foo.pb(foo).ttl, duration_pb2.Duration) + assert foo.ttl.seconds == 120 + assert Foo.pb(foo).ttl.seconds == 120 + + +def test_duration_del(): + class Foo(proto.Message): + ttl = proto.Field(proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + foo = Foo(ttl=timedelta(seconds=900)) + del foo.ttl + assert isinstance(foo.ttl, timedelta) + assert foo.ttl.days == 0 + assert foo.ttl.seconds == 0 + assert foo.ttl.microseconds == 0 + + +def test_duration_nanos_rmw(): + class Foo(proto.Message): + ttl = proto.Field(proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + foo = Foo(ttl=timedelta(microseconds=50)) + assert foo.ttl.microseconds == 50 + assert Foo.pb(foo).ttl.nanos == 50000 + foo.ttl = timedelta(microseconds=25) + assert Foo.pb(foo).ttl.nanos == 25000 + assert foo.ttl.microseconds == 25 + + +def test_timestamp_to_python_idempotent(): + # This path can never run in the current configuration because proto + # values are the only thing ever saved, and `to_python` is a read method. + # + # However, we test idempotency for consistency with `to_proto` and + # general resiliency. + marshal = proto.marshal + py_value = datetime(2012, 4, 21, 15, tzinfo=timezone.utc) + assert marshal.to_python(timestamp_pb2.Timestamp, py_value) is py_value + + +def test_duration_to_python_idempotent(): + # This path can never run in the current configuration because proto + # values are the only thing ever saved, and `to_python` is a read method. + # + # However, we test idempotency for consistency with `to_proto` and + # general resiliency. + marshal = proto.marshal + py_value = timedelta(seconds=240) + assert marshal.to_python(duration_pb2.Duration, py_value) is py_value diff --git a/packages/proto-plus/tests/test_marshal_types_message.py b/packages/proto-plus/tests/test_marshal_types_message.py new file mode 100644 index 000000000000..d1a252aa0ef8 --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_types_message.py @@ -0,0 +1,37 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto +from proto.marshal.types.message import MessageMarshal + + +def test_to_proto(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + message_marshal = MessageMarshal(Foo.pb(), Foo) + foo_pb2_a = message_marshal.to_proto(Foo(bar=42)) + foo_pb2_b = message_marshal.to_proto(Foo.pb()(bar=42)) + foo_pb2_c = message_marshal.to_proto({'bar': 42}) + assert foo_pb2_a == foo_pb2_b == foo_pb2_c + + +def test_to_python(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + message_marshal = MessageMarshal(Foo.pb(), Foo) + foo_a = message_marshal.to_python(Foo(bar=42)) + foo_b = message_marshal.to_python(Foo.pb()(bar=42)) + assert foo_a == foo_b diff --git a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py new file mode 100644 index 000000000000..ad3d1cf20469 --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py @@ -0,0 +1,109 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.protobuf import wrappers_pb2 + +import proto + + +def test_bool_value_init(): + class Foo(proto.Message): + bar = proto.Field(proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) + assert Foo(bar=True).bar is True + assert Foo(bar=False).bar is False + assert Foo().bar is None + + +def test_bool_value_init_dict(): + class Foo(proto.Message): + bar = proto.Field(proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) + assert Foo({'bar': True}).bar is True + assert Foo({'bar': False}).bar is False + assert Foo({'bar': None}).bar is None + + +def test_bool_value_distinction_from_bool(): + class Foo(proto.Message): + bar = proto.Field(proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) + baz = proto.Field(proto.BOOL, number=2) + assert Foo().bar is None + assert Foo().baz is False + + +def test_bool_value_rmw(): + class Foo(proto.Message): + bar = proto.Field(wrappers_pb2.BoolValue, number=1) + baz = proto.Field(wrappers_pb2.BoolValue, number=2) + foo = Foo(bar=False) + assert foo.bar is False + assert foo.baz is None + foo.baz = True + assert foo.baz is True + assert Foo.pb(foo).baz.value is True + foo.bar = None + assert foo.bar is None + assert not Foo.pb(foo).HasField('bar') + + +def test_bool_value_write_bool_value(): + class Foo(proto.Message): + bar = proto.Field(proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) + foo = Foo(bar=True) + foo.bar = wrappers_pb2.BoolValue() + assert foo.bar is False + + +def test_bool_value_del(): + class Foo(proto.Message): + bar = proto.Field(proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) + foo = Foo(bar=False) + assert foo.bar is False + del foo.bar + assert foo.bar is None + + +def test_multiple_types(): + class Foo(proto.Message): + bar = proto.Field(wrappers_pb2.BoolValue, number=1) + baz = proto.Field(wrappers_pb2.Int32Value, number=2) + foo = Foo(bar=True, baz=42) + assert foo.bar is True + assert foo.baz == 42 + + +def test_bool_value_to_python(): + # This path can never run in the current configuration because proto + # values are the only thing ever saved, and `to_python` is a read method. + # + # However, we test idempotency for consistency with `to_proto` and + # general resiliency. + marshal = proto.marshal + assert marshal.to_python(wrappers_pb2.BoolValue, True) is True + assert marshal.to_python(wrappers_pb2.BoolValue, False) is False + assert marshal.to_python(wrappers_pb2.BoolValue, None) is None diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py new file mode 100644 index 000000000000..2a7f1dcc0ee2 --- /dev/null +++ b/packages/proto-plus/tests/test_message.py @@ -0,0 +1,191 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import proto + + +def test_message_constructor_instance(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + foo_original = Foo(bar=42) + foo_copy = Foo(foo_original) + assert foo_original.bar == foo_copy.bar == 42 + assert foo_original == foo_copy + assert foo_original is not foo_copy + assert isinstance(foo_original, Foo) + assert isinstance(foo_copy, Foo) + assert isinstance(Foo.pb(foo_copy), Foo.pb()) + + +def test_message_constructor_underlying_pb2(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + foo_pb2 = Foo.pb()(bar=42) + foo = Foo(foo_pb2) + assert foo.bar == Foo.pb(foo).bar == foo_pb2.bar == 42 + assert foo == foo_pb2 # Not communitive. Nothing we can do about that. + assert foo_pb2 == Foo.pb(foo) + assert foo_pb2 is not Foo.pb(foo) + assert isinstance(foo, Foo) + assert isinstance(Foo.pb(foo), Foo.pb()) + assert isinstance(foo_pb2, Foo.pb()) + + +def test_message_constructor_underlying_pb2_and_kwargs(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + foo_pb2 = Foo.pb()(bar=42) + foo = Foo(foo_pb2, bar=99) + assert foo.bar == Foo.pb(foo).bar == 99 + assert foo_pb2.bar == 42 + assert isinstance(foo, Foo) + assert isinstance(Foo.pb(foo), Foo.pb()) + assert isinstance(foo_pb2, Foo.pb()) + + +def test_message_constructor_dict(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + foo = Foo({'bar': 42}) + assert foo.bar == Foo.pb(foo).bar == 42 + assert foo != {'bar': 42} + assert isinstance(foo, Foo) + assert isinstance(Foo.pb(foo), Foo.pb()) + + +def test_message_constructor_kwargs(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + foo = Foo(bar=42) + assert foo.bar == Foo.pb(foo).bar == 42 + assert isinstance(foo, Foo) + assert isinstance(Foo.pb(foo), Foo.pb()) + + +def test_message_constructor_invalid(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + with pytest.raises(TypeError): + Foo(object()) + + +def test_message_constructor_explicit_qualname(): + class Foo(proto.Message): + __qualname__ = 'Foo' + bar = proto.Field(proto.INT64, number=1) + foo_original = Foo(bar=42) + foo_copy = Foo(foo_original) + assert foo_original.bar == foo_copy.bar == 42 + assert foo_original == foo_copy + assert foo_original is not foo_copy + assert isinstance(foo_original, Foo) + assert isinstance(foo_copy, Foo) + assert isinstance(Foo.pb(foo_copy), Foo.pb()) + + +def test_message_contains_primitive(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + assert 'bar' in Foo(bar=42) + assert 'bar' not in Foo(bar=0) + assert 'bar' not in Foo() + + +def test_message_contains_composite(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + + class Baz(proto.Message): + foo = proto.Field(proto.MESSAGE, number=1, message=Foo) + + assert 'foo' in Baz(foo=Foo(bar=42)) + assert 'foo' in Baz(foo=Foo()) + assert 'foo' not in Baz() + + +def test_message_contains_repeated_primitive(): + class Foo(proto.Message): + bar = proto.RepeatedField(proto.INT64, number=1) + + assert 'bar' in Foo(bar=[1, 1, 2, 3, 5]) + assert 'bar' in Foo(bar=[0]) + assert 'bar' not in Foo(bar=[]) + assert 'bar' not in Foo() + + +def test_message_contains_repeated_composite(): + class Foo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + + class Baz(proto.Message): + foo = proto.RepeatedField(proto.MESSAGE, number=1, message=Foo) + + assert 'foo' in Baz(foo=[Foo(bar=42)]) + assert 'foo' in Baz(foo=[Foo()]) + assert 'foo' not in Baz(foo=[]) + assert 'foo' not in Baz() + + +def test_message_eq_primitives(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + baz = proto.Field(proto.STRING, number=2) + bacon = proto.Field(proto.BOOL, number=3) + + assert Foo() == Foo() + assert Foo(bar=42, baz='42') == Foo(bar=42, baz='42') + assert Foo(bar=42, baz='42') != Foo(baz='42') + assert Foo(bar=42, bacon=True) == Foo(bar=42, bacon=True) + assert Foo(bar=42, bacon=True) != Foo(bar=42) + assert Foo(bar=42, baz='42', bacon=True) != Foo(bar=42, bacon=True) + assert Foo(bacon=False) == Foo() + assert Foo(bacon=True) != Foo(bacon=False) + assert Foo(bar=21 * 2) == Foo(bar=42) + assert Foo() == Foo(bar=0) + assert Foo() == Foo(bar=0, baz='', bacon=False) + assert Foo() != Foo(bar=0, baz='0', bacon=False) + + +def test_message_serialize(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + baz = proto.Field(proto.STRING, number=2) + bacon = proto.Field(proto.BOOL, number=3) + + foo = Foo(bar=42, bacon=True) + assert Foo.serialize(foo) == Foo.pb(foo).SerializeToString() + + +def test_message_deserialize(): + class OldFoo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class NewFoo(proto.Message): + bar = proto.Field(proto.INT64, number=1) + + serialized = OldFoo.serialize(OldFoo(bar=42)) + new_foo = NewFoo.deserialize(serialized) + assert isinstance(new_foo, NewFoo) + assert new_foo.bar == 42 + + +def test_message_pb(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + assert isinstance(Foo.pb(Foo()), Foo.pb()) + with pytest.raises(TypeError): + Foo.pb(object()) diff --git a/packages/proto-plus/tests/test_message_all.py b/packages/proto-plus/tests/test_message_all.py new file mode 100644 index 000000000000..a2ee17985aa8 --- /dev/null +++ b/packages/proto-plus/tests/test_message_all.py @@ -0,0 +1,65 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +from unittest import mock + +from google.protobuf import wrappers_pb2 + +import proto + + +def test_message_creation_all(): + __all__ = ('Foo', 'Bar', 'Baz') # noqa: F841 + + # We want to fake a module, but modules have attribute access, and + # `frame.f_locals` is a dictionary. Since we only actually care about + # getattr, this is reasonably easy to shim over. + frame = inspect.currentframe() + with mock.patch.object(inspect, 'getmodule') as getmodule: + getmodule.side_effect = lambda *a: View(frame.f_locals) + + class Foo(proto.Message): + a = proto.Field(wrappers_pb2.Int32Value, number=1) + + class Bar(proto.Message): + b = proto.Field(proto.MESSAGE, number=1, message=Foo) + + assert not Foo.pb() + assert not Bar.pb() + + class Baz(proto.Message): + c = proto.Field(wrappers_pb2.BoolValue, number=1) + + assert Foo.pb() + assert Bar.pb() + assert Baz.pb() + + foo = Foo(a=12) + bar = Bar(b=Foo(a=24)) + baz = Baz(c=False) + assert foo.a == 12 + assert bar.b.a == 24 + assert baz.c is False + + +class View: + """A view around a mapping, for attribute-like access.""" + def __init__(self, mapping): + self._mapping = mapping + + def __getattr__(self, name): + if name not in self._mapping: + raise AttributeError + return self._mapping[name] diff --git a/packages/proto-plus/tests/test_message_nested.py b/packages/proto-plus/tests/test_message_nested.py new file mode 100644 index 000000000000..e3cbbfd150ec --- /dev/null +++ b/packages/proto-plus/tests/test_message_nested.py @@ -0,0 +1,59 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +def test_singly_nested_message(): + class Foo(proto.Message): + class Bar(proto.Message): + value = proto.Field(proto.INT32, number=1) + bar = proto.Field(proto.MESSAGE, number=1, message=Bar) + + foo = Foo(bar=Foo.Bar(value=42)) + assert foo.bar.value == 42 + + +def test_multiply_nested_message(): + class Foo(proto.Message): + class Bar(proto.Message): + class Baz(proto.Message): + value = proto.Field(proto.INT32, number=1) + baz = proto.Field(proto.MESSAGE, number=1, message=Baz) + bar = proto.Field(proto.MESSAGE, number=1, message=Bar) + + foo = Foo(bar=Foo.Bar(baz=Foo.Bar.Baz(value=42))) + assert foo.bar.baz.value == 42 + + +def test_forking_nested_messages(): + class Foo(proto.Message): + class Bar(proto.Message): + spam = proto.Field(proto.STRING, number=1) + eggs = proto.Field(proto.BOOL, number=2) + + class Baz(proto.Message): + class Bacon(proto.Message): + value = proto.Field(proto.INT32, number=1) + bacon = proto.Field(proto.MESSAGE, number=1, message=Bacon) + bar = proto.Field(proto.MESSAGE, number=1, message=Bar) + baz = proto.Field(proto.MESSAGE, number=2, message=Baz) + + foo = Foo( + bar={'spam': 'xyz', 'eggs': False}, + baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), + ) + assert foo.bar.spam == 'xyz' + assert not foo.bar.eggs + assert foo.baz.bacon.value == 42 From 83e3d64bc70f6522f16741cf581b17f7426e1494 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 22 Nov 2018 17:07:18 -0800 Subject: [PATCH 002/272] fix: Correct documentation links in README. (#4) --- packages/proto-plus/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index de6883fcdb5f..7fade7cba435 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -18,7 +18,7 @@ Documentation `Documentation`_ is available on Read the Docs. -.. _documentation: https://proto-plus-python.readthedocs.io/ +.. _documentation: https://proto-plus-python.readthedocs.io/en/latest/ Disclaimer @@ -30,7 +30,7 @@ This is not an official Google product. .. |release level| image:: https://img.shields.io/badge/release%20level-alpha-red.svg?style=flat :target: https://cloud.google.com/terms/launch-stages .. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest - :target: https://gapic-generator-python.readthedocs.io/ + :target: https://proto-plus-python.readthedocs.io/en/latest/ .. |ci| image:: https://circleci.com/gh/googleapis/proto-plus-python.svg?style=shield :target: https://circleci.com/gh/googleapis/proto-plus-python .. |codecov| image:: https://codecov.io/gh/googleapis/proto-plus-python/graph/badge.svg From e72a5e687b3c54e67e8be523f407561d54cde089 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 22 Nov 2018 17:08:39 -0800 Subject: [PATCH 003/272] chore: Use tags that Read the Docs likes. (#5) Drop the "v" prefix from release tags, since RTD looks cleaner without them. --- packages/proto-plus/.circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index 8d5e876ca744..8017c3a0f24c 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -45,7 +45,7 @@ workflows: branches: ignore: /.*/ tags: - only: /^v\d+\.\d+\.\d+((a|b|rc)\d+)?$/ + only: /^\d+\.\d+\.\d+((a|b|rc)\d+)?$/ jobs: unit-3.5: docker: From a00460bc3ddd27e15619c83dcb705861c2984b4e Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Fri, 21 Dec 2018 11:47:55 -0800 Subject: [PATCH 004/272] Update version number to 0.1.0 (#6) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index b3db993dfe3f..1a05dd1b5600 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name='proto-plus', - version='0.1.0a6', + version='0.1.0', license='Apache 2.0', author='Luke Sneeringer', author_email='lukesneeringer@google.com', From 91105943713cda7e946b3bd67a809e3aa5ab56d6 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Tue, 25 Dec 2018 16:38:29 -0800 Subject: [PATCH 005/272] [feat] Make serialize coerce the input. (#7) This is important because it makes gRPC's `request_serializer` be more liberal in what it accepts when given `serialize`. --- packages/proto-plus/proto/message.py | 18 ++++++++++++------ packages/proto-plus/tests/test_message.py | 10 ++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 0f46b3bd9990..03e49d394eb6 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -257,19 +257,24 @@ def __prepare__(mcls, name, bases, **kwargs): def meta(cls): return cls._meta - def pb(cls, obj=None): + def pb(cls, obj=None, *, coerce: bool = False): """Return the underlying protobuf Message class or instance. Args: obj: If provided, and an instance of ``cls``, return the underlying protobuf instance. + coerce (bool): If provided, will attempt to coerce ``obj`` to + ``cls`` if it is not already an instance. """ if obj is None: return cls.meta.pb if not isinstance(obj, cls): - raise TypeError('%r is not an instance of %s' % ( - obj, cls.__name__, - )) + if coerce: + obj = cls(obj) + else: + raise TypeError('%r is not an instance of %s' % ( + obj, cls.__name__, + )) return obj._pb def wrap(cls, pb): @@ -285,12 +290,13 @@ def serialize(cls, instance) -> bytes: """Return the serialized proto. Args: - instance: An instance of this message type. + instance: An instance of this message type, or something + compatible (accepted by the type's constructor). Returns: bytes: The serialized representation of the protocol buffer. """ - return cls.pb(instance).SerializeToString() + return cls.pb(instance, coerce=True).SerializeToString() def deserialize(cls, payload: bytes) -> 'Message': """Given a serialized proto, deserialize it into a Message instance. diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index 2a7f1dcc0ee2..c6520c197caa 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -169,6 +169,16 @@ class Foo(proto.Message): assert Foo.serialize(foo) == Foo.pb(foo).SerializeToString() +def test_message_dict_serialize(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + baz = proto.Field(proto.STRING, number=2) + bacon = proto.Field(proto.BOOL, number=3) + + foo = {'bar': 42, 'bacon': True} + assert Foo.serialize(foo) == Foo.pb(foo, coerce=True).SerializeToString() + + def test_message_deserialize(): class OldFoo(proto.Message): bar = proto.Field(proto.INT32, number=1) From 04514e28bd5fc4e5991d5ed8401a5003d655165b Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Wed, 26 Dec 2018 18:46:41 -0800 Subject: [PATCH 006/272] [refactor] Make collection wrappers take their marshal object. (#8) This commit makes collections accept their marshal object, instead of referencing a singleton. This will make it easier to make marshals have a defined scope. --- .../proto/marshal/collections/__init__.py | 24 +++ .../proto/marshal/collections/map.py | 81 +++++++++ .../proto/marshal/collections/repeated.py | 127 +++++++++++++ packages/proto-plus/proto/marshal/marshal.py | 170 +----------------- 4 files changed, 238 insertions(+), 164 deletions(-) create mode 100644 packages/proto-plus/proto/marshal/collections/__init__.py create mode 100644 packages/proto-plus/proto/marshal/collections/map.py create mode 100644 packages/proto-plus/proto/marshal/collections/repeated.py diff --git a/packages/proto-plus/proto/marshal/collections/__init__.py b/packages/proto-plus/proto/marshal/collections/__init__.py new file mode 100644 index 000000000000..fffb6af92c4f --- /dev/null +++ b/packages/proto-plus/proto/marshal/collections/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .map import MapComposite +from .repeated import Repeated +from .repeated import RepeatedComposite + + +__all__ = ( + 'MapComposite', + 'Repeated', + 'RepeatedComposite', +) diff --git a/packages/proto-plus/proto/marshal/collections/map.py b/packages/proto-plus/proto/marshal/collections/map.py new file mode 100644 index 000000000000..e98899a98cdc --- /dev/null +++ b/packages/proto-plus/proto/marshal/collections/map.py @@ -0,0 +1,81 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections + +from proto.utils import cached_property + + +class MapComposite(collections.MutableMapping): + """A view around a mutable sequence in protocol buffers. + + This implements the full Python MutableMapping interface, but all methods + modify the underlying field container directly. + """ + @cached_property + def _pb_type(self): + """Return the protocol buffer type for this sequence.""" + # Huzzah, another hack. Still less bad than RepeatedComposite. + return type(self.pb.GetEntryClass()().value) + + def __init__(self, sequence, *, marshal): + """Initialize a wrapper around a protobuf map. + + Args: + sequence: A protocol buffers map. + marshal (~.MarshalRegistry): An instantiated marshal, used to + convert values going to and from this map. + """ + self._pb = sequence + self._marshal = marshal + + def __contains__(self, key): + # Protocol buffers is so permissive that querying for the existence + # of a key will in of itself create it. + # + # By taking a tuple of the keys and querying that, we avoid sending + # the lookup to protocol buffers and therefore avoid creating the key. + return key in tuple(self.keys()) + + def __getitem__(self, key): + # We handle raising KeyError ourselves, because otherwise protocol + # buffers will create the key if it does not exist. + if key not in self: + raise KeyError(key) + return self._marshal.to_python(self._pb_type, self.pb[key]) + + def __setitem__(self, key, value): + pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) + + # Directly setting a key is not allowed; however, protocol buffers + # is so permissive that querying for the existence of a key will in + # of itself create it. + # + # Therefore, we create a key that way (clearing any fields that may + # be set) and then merge in our values. + self.pb[key].Clear() + self.pb[key].MergeFrom(pb_value) + + def __delitem__(self, key): + self.pb.pop(key) + + def __len__(self): + return len(self.pb) + + def __iter__(self): + return iter(self.pb) + + @property + def pb(self): + return self._pb diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py new file mode 100644 index 000000000000..e51d7b328c86 --- /dev/null +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -0,0 +1,127 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections +import copy + +from proto.utils import cached_property + + +class Repeated(collections.MutableSequence): + """A view around a mutable sequence in protocol buffers. + + This implements the full Python MutableSequence interface, but all methods + modify the underlying field container directly. + """ + def __init__(self, sequence, *, marshal): + """Initialize a wrapper around a protobuf repeated field. + + Args: + sequence: A protocol buffers repeated field. + marshal (~.MarshalRegistry): An instantiated marshal, used to + convert values going to and from this map. + """ + self._pb = sequence + self._marshal = marshal + + def __copy__(self): + """Copy this object and return the copy.""" + return type(self)(self.pb[:], marshal=self._marshal) + + def __delitem__(self, key): + """Delete the given item.""" + del self.pb[key] + + def __eq__(self, other): + if hasattr(other, 'pb'): + return tuple(self.pb) == tuple(other.pb) + return tuple(self.pb) == tuple(other) + + def __getitem__(self, key): + """Return the given item.""" + return self.pb[key] + + def __len__(self): + """Return the length of the sequence.""" + return len(self.pb) + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return repr(self.pb) + + def __setitem__(self, key, value): + self.pb[key] = value + + def insert(self, index: int, value): + """Insert ``value`` in the sequence before ``index``.""" + self.pb.insert(index, value) + + def sort(self, *, key: str = None, reverse: bool = False): + """Stable sort *IN PLACE*.""" + self.pb.sort(key=key, reverse=reverse) + + @property + def pb(self): + return self._pb + + +class RepeatedComposite(Repeated): + """A view around a mutable sequence of messages in protocol buffers. + + This implements the full Python MutableSequence interface, but all methods + modify the underlying field container directly. + """ + @cached_property + def _pb_type(self): + """Return the protocol buffer type for this sequence.""" + # There is no public-interface mechanism to determine the type + # of what should go in the list (and the C implementation seems to + # have no exposed mechanism at all). + # + # If the list has members, use the existing list members to + # determine the type. + if len(self.pb) > 0: + return type(self.pb[0]) + + # We have no members in the list. + # In order to get the type, we create a throw-away copy and add a + # blank member to it. + canary = copy.deepcopy(self.pb).add() + return type(canary) + + def __getitem__(self, key): + return self._marshal.to_python(self._pb_type, self.pb[key]) + + def __setitem__(self, key, value): + pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) + + # Protocol buffers does not define a useful __setitem__, so we + # have to pop everything after this point off the list and reload it. + after = [pb_value] + while self.pb[key:]: + after.append(self.pb.pop(key)) + self.pb.extend(after) + + def insert(self, index: int, value): + """Insert ``value`` in the sequence before ``index``.""" + pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) + + # Protocol buffers does not define a useful insert, so we have + # to pop everything after this point off the list and reload it. + after = [pb_value] + while self.pb[index:]: + after.append(self.pb.pop(index)) + self.pb.extend(after) diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index 1cc4fcc7ea16..d2ed7a14aa06 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. - import abc -import collections -import copy from google.protobuf import message from google.protobuf import duration_pb2 @@ -23,9 +20,11 @@ from google.protobuf import wrappers_pb2 from proto.marshal import containers +from proto.marshal.collections import MapComposite +from proto.marshal.collections import Repeated +from proto.marshal.collections import RepeatedComposite from proto.marshal.types import dates from proto.marshal.types import wrappers -from proto.utils import cached_property class Rule(abc.ABC): @@ -137,13 +136,13 @@ def to_python(self, proto_type, value, *, absent: bool = None): # Internal protobuf has its own special type for lists of values. # Return a view around it that implements MutableSequence. if isinstance(value, containers.repeated_composite_types): - return RepeatedComposite(value) + return RepeatedComposite(value, marshal=self) if isinstance(value, containers.repeated_scalar_types): - return Repeated(value) + return Repeated(value, marshal=self) # Same thing for maps of messages. if isinstance(value, containers.map_composite_types): - return MapComposite(value) + return MapComposite(value, marshal=self) # Convert ordinary values. rule = self._registry.get(proto_type, self._noop) @@ -190,163 +189,6 @@ def to_proto(self, proto_type, value, *, strict: bool = False): return pb_value -class Repeated(collections.MutableSequence): - """A view around a mutable sequence in protocol buffers. - - This implements the full Python MutableSequence interface, but all methods - modify the underlying field container directly. - """ - def __init__(self, sequence): - self._pb = sequence - - def __copy__(self): - """Copy this object and return the copy.""" - return type(self)(sequence=self.pb[:]) - - def __delitem__(self, key): - """Delete the given item.""" - del self.pb[key] - - def __eq__(self, other): - if hasattr(other, 'pb'): - return tuple(self.pb) == tuple(other.pb) - return tuple(self.pb) == tuple(other) - - def __getitem__(self, key): - """Return the given item.""" - return self.pb[key] - - def __len__(self): - """Return the length of the sequence.""" - return len(self.pb) - - def __ne__(self, other): - return not self == other - - def __repr__(self): - return repr(self.pb) - - def __setitem__(self, key, value): - self.pb[key] = value - - def insert(self, index: int, value): - """Insert ``value`` in the sequence before ``index``.""" - self.pb.insert(index, value) - - def sort(self, *, key: str = None, reverse: bool = False): - """Stable sort *IN PLACE*.""" - self.pb.sort(key=key, reverse=reverse) - - @property - def pb(self): - return self._pb - - -class RepeatedComposite(Repeated): - """A view around a mutable sequence of messages in protocol buffers. - - This implements the full Python MutableSequence interface, but all methods - modify the underlying field container directly. - """ - @cached_property - def _pb_type(self): - """Return the protocol buffer type for this sequence.""" - # There is no public-interface mechanism to determine the type - # of what should go in the list (and the C implementation seems to - # have no exposed mechanism at all). - # - # If the list has members, use the existing list members to - # determine the type. - if len(self.pb) > 0: - return type(self.pb[0]) - - # We have no members in the list. - # In order to get the type, we create a throw-away copy and add a - # blank member to it. - canary = copy.deepcopy(self.pb).add() - return type(canary) - - def __getitem__(self, key): - return marshal.to_python(self._pb_type, self.pb[key]) - - def __setitem__(self, key, value): - pb_value = marshal.to_proto(self._pb_type, value, strict=True) - - # Protocol buffers does not define a useful __setitem__, so we - # have to pop everything after this point off the list and reload it. - after = [pb_value] - while self.pb[key:]: - after.append(self.pb.pop(key)) - self.pb.extend(after) - - def insert(self, index: int, value): - """Insert ``value`` in the sequence before ``index``.""" - pb_value = marshal.to_proto(self._pb_type, value, strict=True) - - # Protocol buffers does not define a useful insert, so we have - # to pop everything after this point off the list and reload it. - after = [pb_value] - while self.pb[index:]: - after.append(self.pb.pop(index)) - self.pb.extend(after) - - -class MapComposite(collections.MutableMapping): - """A view around a mutable sequence in protocol buffers. - - This implements the full Python MutableMapping interface, but all methods - modify the underlying field container directly. - """ - @cached_property - def _pb_type(self): - """Return the protocol buffer type for this sequence.""" - # Huzzah, another hack. Still less bad than RepeatedComposite. - return type(self.pb.GetEntryClass()().value) - - def __init__(self, sequence): - self._pb = sequence - - def __contains__(self, key): - # Protocol buffers is so permissive that querying for the existence - # of a key will in of itself create it. - # - # By taking a tuple of the keys and querying that, we avoid sending - # the lookup to protocol buffers and therefore avoid creating the key. - return key in tuple(self.keys()) - - def __getitem__(self, key): - # We handle raising KeyError ourselves, because otherwise protocol - # buffers will create the key if it does not exist. - if key not in self: - raise KeyError(key) - return marshal.to_python(self._pb_type, self.pb[key]) - - def __setitem__(self, key, value): - pb_value = marshal.to_proto(self._pb_type, value, strict=True) - - # Directly setting a key is not allowed; however, protocol buffers - # is so permissive that querying for the existence of a key will in - # of itself create it. - # - # Therefore, we create a key that way (clearing any fields that may - # be set) and then merge in our values. - self.pb[key].Clear() - self.pb[key].MergeFrom(pb_value) - - def __delitem__(self, key): - self.pb.pop(key) - - def __len__(self): - return len(self.pb) - - def __iter__(self): - return iter(self.pb) - - @property - def pb(self): - return self._pb - - class NoopMarshal: """A catch-all marshal that does nothing.""" From 2f401882de3999fa4eddac63eac22373e805891c Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Wed, 26 Dec 2018 20:28:00 -0800 Subject: [PATCH 007/272] [feat] Allow multiple marshals. (#9) This commit removes the "marshal as singleton" component, allowing different marshals to exist in tandem. In general, each package should likely use its own marshal. --- packages/proto-plus/.coveragerc | 2 +- packages/proto-plus/proto/__init__.py | 4 +- packages/proto-plus/proto/marshal/__init__.py | 7 ++ .../marshal/{containers.py => compat.py} | 0 packages/proto-plus/proto/marshal/marshal.py | 102 +++++++++++------- .../marshal/{types => rules}/__init__.py | 0 .../proto/marshal/{types => rules}/dates.py | 4 +- .../proto/marshal/{types => rules}/message.py | 2 +- .../marshal/{types => rules}/wrappers.py | 20 ++-- packages/proto-plus/proto/message.py | 24 +++-- packages/proto-plus/tests/conftest.py | 10 +- .../proto-plus/tests/test_marshal_register.py | 21 ++-- .../tests/test_marshal_types_dates.py | 5 +- .../tests/test_marshal_types_message.py | 16 +-- .../tests/test_marshal_types_wrappers_bool.py | 3 +- 15 files changed, 137 insertions(+), 83 deletions(-) rename packages/proto-plus/proto/marshal/{containers.py => compat.py} (100%) rename packages/proto-plus/proto/marshal/{types => rules}/__init__.py (100%) rename packages/proto-plus/proto/marshal/{types => rules}/dates.py (98%) rename packages/proto-plus/proto/marshal/{types => rules}/message.py (98%) rename packages/proto-plus/proto/marshal/{types => rules}/wrappers.py (83%) diff --git a/packages/proto-plus/.coveragerc b/packages/proto-plus/.coveragerc index 89353bb9897f..55fda8f09cab 100644 --- a/packages/proto-plus/.coveragerc +++ b/packages/proto-plus/.coveragerc @@ -5,7 +5,7 @@ branch = True fail_under = 100 show_missing = True omit = - proto/marshal/containers.py + proto/marshal/compat.py exclude_lines = # Re-enable the standard pragma pragma: NO COVER diff --git a/packages/proto-plus/proto/__init__.py b/packages/proto-plus/proto/__init__.py index 8f69ef55c21a..577eac40f35c 100644 --- a/packages/proto-plus/proto/__init__.py +++ b/packages/proto-plus/proto/__init__.py @@ -15,7 +15,7 @@ from .fields import Field from .fields import MapField from .fields import RepeatedField -from .marshal.marshal import marshal +from .marshal import Marshal from .message import Message from .primitives import ProtoType @@ -43,7 +43,7 @@ 'Field', 'MapField', 'RepeatedField', - 'marshal', + 'Marshal', 'Message', # Expose the types directly. diff --git a/packages/proto-plus/proto/marshal/__init__.py b/packages/proto-plus/proto/marshal/__init__.py index b0c7da3d7725..71f1c7218e05 100644 --- a/packages/proto-plus/proto/marshal/__init__.py +++ b/packages/proto-plus/proto/marshal/__init__.py @@ -11,3 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +from .marshal import Marshal + + +__all__ = ( + 'Marshal', +) diff --git a/packages/proto-plus/proto/marshal/containers.py b/packages/proto-plus/proto/marshal/compat.py similarity index 100% rename from packages/proto-plus/proto/marshal/containers.py rename to packages/proto-plus/proto/marshal/compat.py diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index d2ed7a14aa06..4bd6cb5605f2 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -19,12 +19,12 @@ from google.protobuf import timestamp_pb2 from google.protobuf import wrappers_pb2 -from proto.marshal import containers +from proto.marshal import compat from proto.marshal.collections import MapComposite from proto.marshal.collections import Repeated from proto.marshal.collections import RepeatedComposite -from proto.marshal.types import dates -from proto.marshal.types import wrappers +from proto.marshal.rules import dates +from proto.marshal.rules import wrappers class Rule(abc.ABC): @@ -37,8 +37,8 @@ def __subclasshook__(cls, C): return NotImplemented -class MarshalRegistry: - """A class to translate between protocol buffers and Python classes. +class BaseMarshal: + """The base class to translate between protobuf and Python classes. Protocol buffers defines many common types (e.g. Timestamp, Duration) which also exist in the Python standard library. The marshal essentially @@ -52,14 +52,12 @@ class MarshalRegistry: the declared field type is still used. This means that, if appropriate, multiple protocol buffer types may use the same Python type. - The marshal is intended to be a singleton; this module instantiates - and exports one marshal, which is imported throughout the rest of this - library. This allows for an advanced case where user code registers - additional types to be marshaled. + The primary implementation of this is :class:`Marshal`, which should + usually be used instead of this class directly. """ def __init__(self): - self._registry = {} - self._noop = NoopMarshal() + self._rules = {} + self._noop = NoopRule() self.reset() def register(self, proto_type: type, rule: Rule = None): @@ -73,7 +71,7 @@ def register(self, proto_type: type, rule: Rule = None): This function can also be used as a decorator:: @marshal.register(timestamp_pb2.Timestamp) - class TimestampMarshal: + class TimestampRule: ... In this case, the class will be initialized for you with zero @@ -97,7 +95,7 @@ class TimestampMarshal: '`to_proto` and `to_python` methods.') # Register the rule. - self._registry[proto_type] = rule + self._rules[proto_type] = rule return # Create an inner function that will register an instance of the @@ -109,43 +107,43 @@ def register_rule_class(rule_class: type): '`to_proto` and `to_python` methods.') # Register the rule class. - self._registry[proto_type] = rule_class() + self._rules[proto_type] = rule_class() return rule_class return register_rule_class def reset(self): """Reset the registry to its initial state.""" - self._registry.clear() + self._rules.clear() # Register date and time wrappers. - self.register(timestamp_pb2.Timestamp, dates.TimestampMarshal()) - self.register(duration_pb2.Duration, dates.DurationMarshal()) + self.register(timestamp_pb2.Timestamp, dates.TimestampRule()) + self.register(duration_pb2.Duration, dates.DurationRule()) # Register nullable primitive wrappers. - self.register(wrappers_pb2.BoolValue, wrappers.BoolValueMarshal()) - self.register(wrappers_pb2.BytesValue, wrappers.BytesValueMarshal()) - self.register(wrappers_pb2.DoubleValue, wrappers.DoubleValueMarshal()) - self.register(wrappers_pb2.FloatValue, wrappers.FloatValueMarshal()) - self.register(wrappers_pb2.Int32Value, wrappers.Int32ValueMarshal()) - self.register(wrappers_pb2.Int64Value, wrappers.Int64ValueMarshal()) - self.register(wrappers_pb2.StringValue, wrappers.StringValueMarshal()) - self.register(wrappers_pb2.UInt32Value, wrappers.UInt32ValueMarshal()) - self.register(wrappers_pb2.UInt64Value, wrappers.UInt64ValueMarshal()) + self.register(wrappers_pb2.BoolValue, wrappers.BoolValueRule()) + self.register(wrappers_pb2.BytesValue, wrappers.BytesValueRule()) + self.register(wrappers_pb2.DoubleValue, wrappers.DoubleValueRule()) + self.register(wrappers_pb2.FloatValue, wrappers.FloatValueRule()) + self.register(wrappers_pb2.Int32Value, wrappers.Int32ValueRule()) + self.register(wrappers_pb2.Int64Value, wrappers.Int64ValueRule()) + self.register(wrappers_pb2.StringValue, wrappers.StringValueRule()) + self.register(wrappers_pb2.UInt32Value, wrappers.UInt32ValueRule()) + self.register(wrappers_pb2.UInt64Value, wrappers.UInt64ValueRule()) def to_python(self, proto_type, value, *, absent: bool = None): # Internal protobuf has its own special type for lists of values. # Return a view around it that implements MutableSequence. - if isinstance(value, containers.repeated_composite_types): + if isinstance(value, compat.repeated_composite_types): return RepeatedComposite(value, marshal=self) - if isinstance(value, containers.repeated_scalar_types): + if isinstance(value, compat.repeated_scalar_types): return Repeated(value, marshal=self) # Same thing for maps of messages. - if isinstance(value, containers.map_composite_types): + if isinstance(value, compat.map_composite_types): return MapComposite(value, marshal=self) # Convert ordinary values. - rule = self._registry.get(proto_type, self._noop) + rule = self._rules.get(proto_type, self._noop) return rule.to_python(value, absent=absent) def to_proto(self, proto_type, value, *, strict: bool = False): @@ -172,7 +170,7 @@ def to_proto(self, proto_type, value, *, strict: bool = False): for k, v in value.items()} # Convert ordinary values. - rule = self._registry.get(proto_type, self._noop) + rule = self._rules.get(proto_type, self._noop) pb_value = rule.to_proto(value) # Sanity check: If we are in strict mode, did we get the value we want? @@ -189,8 +187,42 @@ def to_proto(self, proto_type, value, *, strict: bool = False): return pb_value -class NoopMarshal: - """A catch-all marshal that does nothing.""" +class Marshal(BaseMarshal): + """The translator between protocol buffer and Python instances. + + The bulk of the implementation is in :class:`BaseMarshal`. This class + adds identity tracking: multiple instantiations of :class:`Marshal` with + the same name will provide the same instance. + """ + _instances = {} + + def __new__(cls, *, name: str): + """Create a marshal instance. + + Args: + name (str): The name of the marshal. Instantiating multiple + marshals with the same ``name`` argument will provide the + same marshal each time. + """ + if name not in cls._instances: + cls._instances[name] = super().__new__(cls) + return cls._instances[name] + + def __init__(self, *, name: str): + """Instantiate a marshal. + + Args: + name (str): The name of the marshal. Instantiating multiple + marshals with the same ``name`` argument will provide the + same marshal each time. + """ + self._name = name + if not hasattr(self, '_rules'): + super().__init__() + + +class NoopRule: + """A catch-all rule that does nothing.""" def to_python(self, pb_value, *, absent: bool = None): return pb_value @@ -199,8 +231,6 @@ def to_proto(self, value): return value -marshal = MarshalRegistry() - __all__ = ( - 'marshal', + 'Marshal', ) diff --git a/packages/proto-plus/proto/marshal/types/__init__.py b/packages/proto-plus/proto/marshal/rules/__init__.py similarity index 100% rename from packages/proto-plus/proto/marshal/types/__init__.py rename to packages/proto-plus/proto/marshal/rules/__init__.py diff --git a/packages/proto-plus/proto/marshal/types/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py similarity index 98% rename from packages/proto-plus/proto/marshal/types/dates.py rename to packages/proto-plus/proto/marshal/rules/dates.py index 2f54d274fb83..b365fdf2165c 100644 --- a/packages/proto-plus/proto/marshal/types/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -20,7 +20,7 @@ from google.protobuf import timestamp_pb2 -class TimestampMarshal: +class TimestampRule: """A marshal between Python datetimes and protobuf timestamps. Note: Python datetimes are less precise than protobuf datetimes @@ -47,7 +47,7 @@ def to_proto(self, value) -> timestamp_pb2.Timestamp: return value -class DurationMarshal: +class DurationRule: """A marshal between Python timedeltas and protobuf durations. Note: Python timedeltas are less precise than protobuf durations diff --git a/packages/proto-plus/proto/marshal/types/message.py b/packages/proto-plus/proto/marshal/rules/message.py similarity index 98% rename from packages/proto-plus/proto/marshal/types/message.py rename to packages/proto-plus/proto/marshal/rules/message.py index c74d496df13e..e5ecf17ba632 100644 --- a/packages/proto-plus/proto/marshal/types/message.py +++ b/packages/proto-plus/proto/marshal/rules/message.py @@ -13,7 +13,7 @@ # limitations under the License. -class MessageMarshal: +class MessageRule: """A marshal for converting between a descriptor and proto.Message.""" def __init__(self, descriptor: type, wrapper: type): diff --git a/packages/proto-plus/proto/marshal/types/wrappers.py b/packages/proto-plus/proto/marshal/rules/wrappers.py similarity index 83% rename from packages/proto-plus/proto/marshal/types/wrappers.py rename to packages/proto-plus/proto/marshal/rules/wrappers.py index 5d2d3658d704..bfd4b78efc43 100644 --- a/packages/proto-plus/proto/marshal/types/wrappers.py +++ b/packages/proto-plus/proto/marshal/rules/wrappers.py @@ -15,7 +15,7 @@ from google.protobuf import wrappers_pb2 -class WrapperMarshal: +class WrapperRule: """A marshal for converting the protobuf wrapper classes to Python. This class converts between ``google.protobuf.BoolValue``, @@ -38,46 +38,46 @@ def to_proto(self, value): return value -class DoubleValueMarshal(WrapperMarshal): +class DoubleValueRule(WrapperRule): _proto_type = wrappers_pb2.DoubleValue _python_type = float -class FloatValueMarshal(WrapperMarshal): +class FloatValueRule(WrapperRule): _proto_type = wrappers_pb2.FloatValue _python_type = float -class Int64ValueMarshal(WrapperMarshal): +class Int64ValueRule(WrapperRule): _proto_type = wrappers_pb2.Int64Value _python_type = int -class UInt64ValueMarshal(WrapperMarshal): +class UInt64ValueRule(WrapperRule): _proto_type = wrappers_pb2.UInt64Value _python_type = int -class Int32ValueMarshal(WrapperMarshal): +class Int32ValueRule(WrapperRule): _proto_type = wrappers_pb2.Int32Value _python_type = int -class UInt32ValueMarshal(WrapperMarshal): +class UInt32ValueRule(WrapperRule): _proto_type = wrappers_pb2.UInt32Value _python_type = int -class BoolValueMarshal(WrapperMarshal): +class BoolValueRule(WrapperRule): _proto_type = wrappers_pb2.BoolValue _python_type = bool -class StringValueMarshal(WrapperMarshal): +class StringValueRule(WrapperRule): _proto_type = wrappers_pb2.StringValue _python_type = str -class BytesValueMarshal(WrapperMarshal): +class BytesValueRule(WrapperRule): _proto_type = wrappers_pb2.BytesValue _python_type = bytes diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 03e49d394eb6..e755b585f0e9 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -29,8 +29,8 @@ from proto.fields import Field from proto.fields import MapField from proto.fields import RepeatedField -from proto.marshal.marshal import marshal -from proto.marshal.types.message import MessageMarshal +from proto.marshal import Marshal +from proto.marshal.rules.message import MessageRule from proto.primitives import ProtoType @@ -47,6 +47,7 @@ def __new__(mcls, name, bases, attrs): # A package and full name should be present. package = getattr(Meta, 'package', '') + marshal = Marshal(name=getattr(Meta, 'marshal', package)) local_path = tuple(attrs.get('__qualname__', name).split('.')) # Sanity check: We get the wrong full name if a class is declared @@ -225,6 +226,7 @@ def __new__(mcls, name, bases, attrs): attrs['_meta'] = _MessageInfo( fields=fields, full_name=full_name, + marshal=marshal, options=opts, package=package, ) @@ -345,7 +347,7 @@ def __init__(self, mapping=None, **kwargs): # passed in. # # The `__wrap_original` argument is private API to override - # this behavior, because `MessageMarshal` actually does want to + # this behavior, because `MessageRule` actually does want to # wrap the original argument it was given. The `wrap` method # on the metaclass is the public API for this behavior. if not kwargs.pop('__wrap_original', False): @@ -364,6 +366,7 @@ def __init__(self, mapping=None, **kwargs): # Update the mapping to address any values that need to be # coerced. + marshal = self._meta.marshal for key, value in copy.copy(mapping).items(): pb_type = self._meta.fields[key].pb_type pb_value = marshal.to_proto(pb_type, value) @@ -464,6 +467,7 @@ def __getattr__(self, key): """ pb_type = self._meta.fields[key].pb_type pb_value = getattr(self._pb, key) + marshal = self._meta.marshal return marshal.to_python(pb_type, pb_value, absent=key not in self) def __ne__(self, other): @@ -481,6 +485,7 @@ def __setattr__(self, key, value): """ if key.startswith('_'): return super().__setattr__(key, value) + marshal = self._meta.marshal pb_type = self._meta.fields[key].pb_type pb_value = marshal.to_proto(pb_type, value) @@ -503,11 +508,15 @@ class _MessageInfo: full_name (str): The full name of the message. file_info (~._FileInfo): The file descriptor and messages for the file containing this message. + marshal (~.Marshal): The marshal instance to which this message was + automatically registered. options (~.descriptor_pb2.MessageOptions): Any options that were set on the message. """ - def __init__(self, *, fields: List[Field], package: str, full_name: str, - options: descriptor_pb2.MessageOptions) -> None: + def __init__(self, *, + fields: List[Field], package: str, full_name: str, + marshal: Marshal, options: descriptor_pb2.MessageOptions + ) -> None: self.package = package self.full_name = full_name self.options = options @@ -517,6 +526,7 @@ def __init__(self, *, fields: List[Field], package: str, full_name: str, self.fields_by_number = collections.OrderedDict([ (i.number, i) for i in fields ]) + self.marshal = marshal self._pb = None @property @@ -573,9 +583,9 @@ def generate_file_pb(self): # Register the message with the marshal so it is wrapped # appropriately. proto_plus_message._meta._pb = pb_message - marshal.register( + proto_plus_message._meta.marshal.register( pb_message, - MessageMarshal(pb_message, proto_plus_message) + MessageRule(pb_message, proto_plus_message) ) # Iterate over any fields on the message and, if their type diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index f4bf1084ff48..9e8a59c3048b 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -20,8 +20,8 @@ from google.protobuf import reflection from google.protobuf import symbol_database -import proto -from proto.marshal import types +from proto.marshal import Marshal +from proto.marshal import rules from proto.message import _FileInfo @@ -73,11 +73,11 @@ def pytest_runtest_setup(item): # If the marshal had previously registered the old message classes, # then reload the appropriate modules so the marshal is using the new ones. if 'wrappers_pb2' in reloaded: - imp.reload(types.wrappers) + imp.reload(rules.wrappers) if reloaded.intersection({'timestamp_pb2', 'duration_pb2'}): - imp.reload(types.dates) - proto.marshal.reset() + imp.reload(rules.dates) def pytest_runtest_teardown(item): + Marshal._instances.clear() [i.stop() for i in item._mocks] diff --git a/packages/proto-plus/tests/test_marshal_register.py b/packages/proto-plus/tests/test_marshal_register.py index cb36f3ee4b34..673355e1e3f3 100644 --- a/packages/proto-plus/tests/test_marshal_register.py +++ b/packages/proto-plus/tests/test_marshal_register.py @@ -16,24 +16,27 @@ from google.protobuf import empty_pb2 -import proto +from proto.marshal.marshal import BaseMarshal def test_registration(): - @proto.marshal.register(empty_pb2.Empty) - class Marshal: + marshal = BaseMarshal() + + @marshal.register(empty_pb2.Empty) + class Rule: def to_proto(self, value): return value def to_python(self, value, *, absent=None): return value - assert isinstance(proto.marshal._registry[empty_pb2.Empty], Marshal) + assert isinstance(marshal._rules[empty_pb2.Empty], Rule) def test_invalid_target_registration(): + marshal = BaseMarshal() with pytest.raises(TypeError): - @proto.marshal.register(object) - class Marshal: + @marshal.register(object) + class Rule: def to_proto(self, value): return value @@ -42,12 +45,14 @@ def to_python(self, value, *, absent=None): def test_invalid_marshal_class(): + marshal = BaseMarshal() with pytest.raises(TypeError): - @proto.marshal.register(empty_pb2.Empty) + @marshal.register(empty_pb2.Empty) class Marshal: pass def test_invalid_marshal_rule(): + marshal = BaseMarshal() with pytest.raises(TypeError): - proto.marshal.register(empty_pb2.Empty, rule=object()) + marshal.register(empty_pb2.Empty, rule=object()) diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 0a35ebf98a20..22264c54ff3a 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -20,6 +20,7 @@ from google.protobuf import timestamp_pb2 import proto +from proto.marshal.marshal import BaseMarshal def test_timestamp_read(): @@ -212,7 +213,7 @@ def test_timestamp_to_python_idempotent(): # # However, we test idempotency for consistency with `to_proto` and # general resiliency. - marshal = proto.marshal + marshal = BaseMarshal() py_value = datetime(2012, 4, 21, 15, tzinfo=timezone.utc) assert marshal.to_python(timestamp_pb2.Timestamp, py_value) is py_value @@ -223,6 +224,6 @@ def test_duration_to_python_idempotent(): # # However, we test idempotency for consistency with `to_proto` and # general resiliency. - marshal = proto.marshal + marshal = BaseMarshal() py_value = timedelta(seconds=240) assert marshal.to_python(duration_pb2.Duration, py_value) is py_value diff --git a/packages/proto-plus/tests/test_marshal_types_message.py b/packages/proto-plus/tests/test_marshal_types_message.py index d1a252aa0ef8..49d48e4dd7a8 100644 --- a/packages/proto-plus/tests/test_marshal_types_message.py +++ b/packages/proto-plus/tests/test_marshal_types_message.py @@ -13,17 +13,17 @@ # limitations under the License. import proto -from proto.marshal.types.message import MessageMarshal +from proto.marshal.rules.message import MessageRule def test_to_proto(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) - message_marshal = MessageMarshal(Foo.pb(), Foo) - foo_pb2_a = message_marshal.to_proto(Foo(bar=42)) - foo_pb2_b = message_marshal.to_proto(Foo.pb()(bar=42)) - foo_pb2_c = message_marshal.to_proto({'bar': 42}) + message_rule = MessageRule(Foo.pb(), Foo) + foo_pb2_a = message_rule.to_proto(Foo(bar=42)) + foo_pb2_b = message_rule.to_proto(Foo.pb()(bar=42)) + foo_pb2_c = message_rule.to_proto({'bar': 42}) assert foo_pb2_a == foo_pb2_b == foo_pb2_c @@ -31,7 +31,7 @@ def test_to_python(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) - message_marshal = MessageMarshal(Foo.pb(), Foo) - foo_a = message_marshal.to_python(Foo(bar=42)) - foo_b = message_marshal.to_python(Foo.pb()(bar=42)) + message_rule = MessageRule(Foo.pb(), Foo) + foo_a = message_rule.to_python(Foo(bar=42)) + foo_b = message_rule.to_python(Foo.pb()(bar=42)) assert foo_a == foo_b diff --git a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py index ad3d1cf20469..0a25a48c8768 100644 --- a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py +++ b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py @@ -15,6 +15,7 @@ from google.protobuf import wrappers_pb2 import proto +from proto.marshal.marshal import BaseMarshal def test_bool_value_init(): @@ -103,7 +104,7 @@ def test_bool_value_to_python(): # # However, we test idempotency for consistency with `to_proto` and # general resiliency. - marshal = proto.marshal + marshal = BaseMarshal() assert marshal.to_python(wrappers_pb2.BoolValue, True) is True assert marshal.to_python(wrappers_pb2.BoolValue, False) is False assert marshal.to_python(wrappers_pb2.BoolValue, None) is None From 11fbfbb276562af7cd4d86082cad1aaeef8f7723 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 27 Dec 2018 12:08:34 -0800 Subject: [PATCH 008/272] [feat] Support google.protobuf.Struct (#10) This commit makes the denizens of `google/protobuf/struct.proto` be represented as native Python objects (primitives, sequences, and maps). --- .../proto/marshal/collections/__init__.py | 2 +- .../marshal/collections/{map.py => maps.py} | 0 .../proto/marshal/collections/repeated.py | 5 + packages/proto-plus/proto/marshal/marshal.py | 34 ++- .../proto-plus/proto/marshal/rules/struct.py | 130 +++++++++++ packages/proto-plus/proto/message.py | 2 +- packages/proto-plus/tests/conftest.py | 27 ++- .../tests/test_fields_repeated_composite.py | 12 + .../tests/test_marshal_types_struct.py | 214 ++++++++++++++++++ 9 files changed, 408 insertions(+), 18 deletions(-) rename packages/proto-plus/proto/marshal/collections/{map.py => maps.py} (100%) create mode 100644 packages/proto-plus/proto/marshal/rules/struct.py create mode 100644 packages/proto-plus/tests/test_marshal_types_struct.py diff --git a/packages/proto-plus/proto/marshal/collections/__init__.py b/packages/proto-plus/proto/marshal/collections/__init__.py index fffb6af92c4f..78f4bf2f918f 100644 --- a/packages/proto-plus/proto/marshal/collections/__init__.py +++ b/packages/proto-plus/proto/marshal/collections/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .map import MapComposite +from .maps import MapComposite from .repeated import Repeated from .repeated import RepeatedComposite diff --git a/packages/proto-plus/proto/marshal/collections/map.py b/packages/proto-plus/proto/marshal/collections/maps.py similarity index 100% rename from packages/proto-plus/proto/marshal/collections/map.py rename to packages/proto-plus/proto/marshal/collections/maps.py diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index e51d7b328c86..c1df7a770670 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -102,6 +102,11 @@ def _pb_type(self): canary = copy.deepcopy(self.pb).add() return type(canary) + def __eq__(self, other): + if super().__eq__(other): + return True + return tuple([i for i in self]) == tuple(other) + def __getitem__(self, key): return self._marshal.to_python(self._pb_type, self.pb[key]) diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index 4bd6cb5605f2..24a0df428086 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -17,6 +17,7 @@ from google.protobuf import message from google.protobuf import duration_pb2 from google.protobuf import timestamp_pb2 +from google.protobuf import struct_pb2 from google.protobuf import wrappers_pb2 from proto.marshal import compat @@ -24,6 +25,7 @@ from proto.marshal.collections import Repeated from proto.marshal.collections import RepeatedComposite from proto.marshal.rules import dates +from proto.marshal.rules import struct from proto.marshal.rules import wrappers @@ -130,6 +132,15 @@ def reset(self): self.register(wrappers_pb2.UInt32Value, wrappers.UInt32ValueRule()) self.register(wrappers_pb2.UInt64Value, wrappers.UInt64ValueRule()) + # Register the google.protobuf.Struct wrappers. + # + # These are aware of the marshal that created them, because they + # create RepeatedComposite and MapComposite instances directly and + # need to pass the marshal to them. + self.register(struct_pb2.Value, struct.ValueRule(marshal=self)) + self.register(struct_pb2.ListValue, struct.ListValueRule(marshal=self)) + self.register(struct_pb2.Struct, struct.StructRule(marshal=self)) + def to_python(self, proto_type, value, *, absent: bool = None): # Internal protobuf has its own special type for lists of values. # Return a view around it that implements MutableSequence. @@ -147,14 +158,21 @@ def to_python(self, proto_type, value, *, absent: bool = None): return rule.to_python(value, absent=absent) def to_proto(self, proto_type, value, *, strict: bool = False): - # For our repeated and map view objects, simply return the - # underlying pb. - if isinstance(value, (Repeated, MapComposite)): - return value.pb - - # Convert lists and tuples recursively. - if isinstance(value, (list, tuple)): - return type(value)([self.to_proto(proto_type, i) for i in value]) + # The protos in google/protobuf/struct.proto are exceptional cases, + # because they can and should represent themselves as lists and dicts. + # These cases are handled in their rule classes. + if proto_type not in (struct_pb2.Value, + struct_pb2.ListValue, struct_pb2.Struct): + # For our repeated and map view objects, simply return the + # underlying pb. + if isinstance(value, (Repeated, MapComposite)): + return value.pb + + # Convert lists and tuples recursively. + if isinstance(value, (list, tuple)): + return type(value)( + [self.to_proto(proto_type, i) for i in value], + ) # Convert dictionaries recursively when the proto type is a map. # This is slightly more complicated than converting a list or tuple diff --git a/packages/proto-plus/proto/marshal/rules/struct.py b/packages/proto-plus/proto/marshal/rules/struct.py new file mode 100644 index 000000000000..e6a6f714b978 --- /dev/null +++ b/packages/proto-plus/proto/marshal/rules/struct.py @@ -0,0 +1,130 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections.abc + +from google.protobuf import struct_pb2 + +from proto.marshal.collections import maps +from proto.marshal.collections import repeated + + +class ValueRule: + """A rule to marshal between google.protobuf.Value and Python values.""" + + def __init__(self, *, marshal): + self._marshal = marshal + + def to_python(self, value, *, absent: bool = None): + """Coerce the given value to the appropriate Python type. + + Note that setting ``null_value`` is distinct from not setting + a value, and the absent value will raise an exception. + """ + kind = value.WhichOneof('kind') + if kind == 'null_value': + return None + if kind == 'bool_value': + return bool(value.bool_value) + if kind == 'number_value': + return float(value.number_value) + if kind == 'string_value': + return str(value.string_value) + if kind == 'struct_value': + return self._marshal.to_python( + struct_pb2.Struct, + value.struct_value, + absent=False, + ) + if kind == 'list_value': + return self._marshal.to_python( + struct_pb2.ListValue, + value.list_value, + absent=False, + ) + raise AttributeError + + def to_proto(self, value) -> struct_pb2.Value: + """Return a protobuf Value object representing this value.""" + if isinstance(value, struct_pb2.Value): + return value + if value is None: + return struct_pb2.Value(null_value=0) + if isinstance(value, bool): + return struct_pb2.Value(bool_value=value) + if isinstance(value, (int, float)): + return struct_pb2.Value(number_value=float(value)) + if isinstance(value, str): + return struct_pb2.Value(string_value=value) + if isinstance(value, collections.abc.Sequence): + return struct_pb2.Value( + list_value=self._marshal.to_proto(struct_pb2.ListValue, value), + ) + if isinstance(value, collections.abc.Mapping): + return struct_pb2.Value( + struct_value=self._marshal.to_proto(struct_pb2.Struct, value), + ) + raise ValueError('Unable to coerce value: %r' % value) + + +class ListValueRule: + """A rule translating google.protobuf.ListValue and list-like objects.""" + + def __init__(self, *, marshal): + self._marshal = marshal + + def to_python(self, value, *, absent: bool = None): + """Coerce the given value to a Python sequence.""" + return repeated.RepeatedComposite(value.values, marshal=self._marshal) + + def to_proto(self, value) -> struct_pb2.ListValue: + # We got a proto, or else something we sent originally. + # Preserve the instance we have. + if isinstance(value, struct_pb2.ListValue): + return value + if isinstance(value, repeated.RepeatedComposite): + return struct_pb2.ListValue(values=[v for v in value.pb]) + + # We got a list (or something list-like); convert it. + return struct_pb2.ListValue(values=[ + self._marshal.to_proto(struct_pb2.Value, v) for v in value + ]) + + +class StructRule: + """A rule translating google.protobuf.Struct and dict-like objects.""" + + def __init__(self, *, marshal): + self._marshal = marshal + + def to_python(self, value, *, absent: bool = None): + """Coerce the given value to a Python mapping.""" + return maps.MapComposite(value.fields, marshal=self._marshal) + + def to_proto(self, value) -> struct_pb2.Struct: + # We got a proto, or else something we sent originally. + # Preserve the instance we have. + if isinstance(value, struct_pb2.Struct): + return value + if isinstance(value, maps.MapComposite): + return struct_pb2.Struct( + fields={k: v for k, v in value.pb.items()}, + ) + + # We got a dict (or something dict-like); convert it. + answer = struct_pb2.Struct(fields={ + k: self._marshal.to_proto(struct_pb2.Value, v) + for k, v in value.items() + }) + return answer diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index e755b585f0e9..f4ab31243f08 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -489,7 +489,7 @@ def __setattr__(self, key, value): pb_type = self._meta.fields[key].pb_type pb_value = marshal.to_proto(pb_type, value) - # We *always* clear the existing field. + # Clear the existing field. # This is the only way to successfully write nested falsy values, # because otherwise MergeFrom will no-op on them. self._pb.ClearField(key) diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index 9e8a59c3048b..f32ef5773048 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -57,14 +57,10 @@ def pytest_runtest_setup(item): module = getattr(item.module, name) pool.AddSerializedFile(module.DESCRIPTOR.serialized_pb) fd = pool.FindFileByName(module.DESCRIPTOR.name) - for message_name, descriptor in fd.message_types_by_name.items(): - new_message = reflection.GeneratedProtocolMessageType( - message_name, - (message.Message,), - {'DESCRIPTOR': descriptor, '__module__': None}, - ) - sym_db.RegisterMessage(new_message) - setattr(module, message_name, new_message) + + # Register all the messages to the symbol database and the + # module. Do this recursively if there are nested messages. + _register_messages(module, fd.message_types_by_name, sym_db) # Track which modules had new message classes loaded. # This is used below to wire the new classes into the marshal. @@ -74,6 +70,8 @@ def pytest_runtest_setup(item): # then reload the appropriate modules so the marshal is using the new ones. if 'wrappers_pb2' in reloaded: imp.reload(rules.wrappers) + if 'struct_pb2' in reloaded: + imp.reload(rules.struct) if reloaded.intersection({'timestamp_pb2', 'duration_pb2'}): imp.reload(rules.dates) @@ -81,3 +79,16 @@ def pytest_runtest_setup(item): def pytest_runtest_teardown(item): Marshal._instances.clear() [i.stop() for i in item._mocks] + + +def _register_messages(scope, iterable, sym_db): + """Create and register messages from the file descriptor.""" + for name, descriptor in iterable.items(): + new_msg = reflection.GeneratedProtocolMessageType( + name, + (message.Message,), + {'DESCRIPTOR': descriptor, '__module__': None}, + ) + sym_db.RegisterMessage(new_msg) + setattr(scope, name, new_msg) + _register_messages(new_msg, descriptor.nested_types_by_name, sym_db) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index d9e0b4d9827f..8f12604c4ae6 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -31,9 +31,21 @@ class Baz(proto.Message): baz = Baz(foos=[Foo(bar=42)]) assert len(baz.foos) == 1 + assert baz.foos == baz.foos assert baz.foos[0].bar == 42 +def test_repeated_composite_equality(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[Foo(bar=42)]) + assert baz.foos == baz.foos + + def test_repeated_composite_init_struct(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) diff --git a/packages/proto-plus/tests/test_marshal_types_struct.py b/packages/proto-plus/tests/test_marshal_types_struct.py new file mode 100644 index 000000000000..40f709b4c3eb --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_types_struct.py @@ -0,0 +1,214 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from google.protobuf import struct_pb2 + +import proto + + +def test_value_primitives_read(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + assert Foo(value=3).value == 3.0 + assert Foo(value='3').value == '3' + assert Foo(value=None).value is None + assert Foo(value=False).value is False + assert Foo(value=True).value is True + + +def test_value_primitives_rmw(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo() + foo.value = 3 + assert isinstance(foo.value, float) + assert abs(Foo.pb(foo).value.number_value - 3.0) < 1e-7 + foo.value = False + assert not foo.value + assert foo.value is False + assert Foo.pb(foo).value.WhichOneof('kind') == 'bool_value' + foo.value = None + assert not foo.value + assert foo.value is None + assert Foo.pb(foo).value.WhichOneof('kind') == 'null_value' + + +def test_value_write_pb(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value=struct_pb2.Value(string_value='stringy')) + assert foo.value == 'stringy' + + +def test_value_lists_read(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value=['3', None, 'foo', True]) + assert foo.value == ['3', None, 'foo', True] + + +def test_value_lists_rmw(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value=['3', None, 'foo', True]) + foo.value.append('bar') + foo.value.pop(1) + assert foo.value == ['3', 'foo', True, 'bar'] + + +def test_value_lists_nested(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value=[[True, False], [True, False]]) + foo.value.append([False, True]) + assert foo.value == [[True, False], [True, False], [False, True]] + + +def test_value_lists_struct(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value=[{'foo': 'bar', 'spam': 'eggs'}]) + assert foo.value == [{'foo': 'bar', 'spam': 'eggs'}] + + +def test_value_lists_detachment(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value=['foo', 'bar']) + detached_list = foo.value + detached_list.append('baz') + assert foo.value == ['foo', 'bar', 'baz'] + + +def test_value_structs_read(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value={'foo': True, 'bar': False}) + assert foo.value == {'foo': True, 'bar': False} + + +def test_value_structs_rmw(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value={'foo': True, 'bar': False}) + foo.value['baz'] = 'a string' + assert foo.value == {'foo': True, 'bar': False, 'baz': 'a string'} + + +def test_value_structs_nested(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo(value={'foo': True, 'bar': {'spam': 'eggs'}}) + assert foo.value == {'foo': True, 'bar': {'spam': 'eggs'}} + sv = Foo.pb(foo).value.struct_value + assert sv['foo'] is True + assert sv['bar'].fields['spam'].string_value == 'eggs' + + +def test_value_invalid_value(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + with pytest.raises(ValueError): + Foo(value=object()) + + +def test_value_unset(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + foo = Foo() + assert not hasattr(foo, 'value') + + +def test_list_value_read(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.ListValue, number=1) + + foo = Foo(value=['foo', 'bar', True, {'spam': 'eggs'}]) + assert foo.value == ['foo', 'bar', True, {'spam': 'eggs'}] + + +def test_list_value_pb(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.ListValue, number=1) + + foo = Foo(value=struct_pb2.ListValue(values=[ + struct_pb2.Value(string_value='foo'), + struct_pb2.Value(string_value='bar'), + struct_pb2.Value(bool_value=True), + ])) + assert foo.value == ['foo', 'bar', True] + + +def test_list_value_reassignment(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.ListValue, number=1) + + foo = Foo(value=['foo', 'bar']) + detached = foo.value + detached.append(True) + foo.value = detached + assert foo.value == ['foo', 'bar', True] + + +def test_list_value_invalid(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.ListValue, number=1) + + with pytest.raises(TypeError): + Foo(value=3) + + +def test_struct_read(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Struct, number=1) + + foo = Foo(value={'foo': 'bar', 'bacon': True}) + assert foo.value == {'foo': 'bar', 'bacon': True} + + +def test_struct_pb(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Struct, number=1) + + foo = Foo(value=struct_pb2.Struct(fields={ + 'foo': struct_pb2.Value(string_value='bar'), + 'bacon': struct_pb2.Value(bool_value=True), + })) + assert foo.value == {'foo': 'bar', 'bacon': True} + + +def test_struct_reassignment(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Struct, number=1) + + foo = Foo(value={'foo': 'bar'}) + detached = foo.value + detached['bacon'] = True + foo.value = detached + assert foo.value == {'foo': 'bar', 'bacon': True} From d34a64321710b6056bfd16cc5230fc642c691108 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 27 Dec 2018 12:52:08 -0800 Subject: [PATCH 009/272] [chore] Update docs for Struct. (#11) --- packages/proto-plus/docs/marshal.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/proto-plus/docs/marshal.rst b/packages/proto-plus/docs/marshal.rst index 64a72f74b02b..c657b1839ee6 100644 --- a/packages/proto-plus/docs/marshal.rst +++ b/packages/proto-plus/docs/marshal.rst @@ -23,10 +23,13 @@ Protocol buffer type Python type Nullable ``google.protobuf.FloatValue`` ``float`` Yes ``google.protobuf.Int32Value`` ``int`` Yes ``google.protobuf.Int64Value`` ``int`` Yes +``google.protobuf.ListValue`` ``MutableSequence`` – ``google.protobuf.StringValue`` ``str`` Yes +``google.protobuf.Struct`` ``MutableMapping`` – ``google.protobuf.Timestamp`` ``datetime.datetime`` Yes ``google.protobuf.UInt32Value`` ``int`` Yes ``google.protobuf.UInt64Value`` ``int`` Yes +``google.protobuf.Value`` JSON-encodable values Yes =================================== ======================= ======== .. note:: From 6678df5670ea8889ea7e00e80a9e345c094e9833 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 27 Dec 2018 12:55:17 -0800 Subject: [PATCH 010/272] Version bump to 0.2.0 (#12) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 1a05dd1b5600..4e20a551845b 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name='proto-plus', - version='0.1.0', + version='0.2.0', license='Apache 2.0', author='Luke Sneeringer', author_email='lukesneeringer@google.com', From 9bc8e50df6f1d4498588264bd1ae416544583d87 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 27 Dec 2018 14:17:40 -0800 Subject: [PATCH 011/272] [fix] Nested messages with a package should build. (#13) --- packages/proto-plus/proto/message.py | 2 +- packages/proto-plus/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index f4ab31243f08..815bf88c8ac9 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -220,7 +220,7 @@ def __new__(mcls, name, bases, attrs): if len(local_path) == 1: file_info.descriptor.message_type.add().MergeFrom(desc) else: - file_info.nested[tuple(full_name.split('.'))] = desc + file_info.nested[local_path] = desc # Create the MessageInfo instance to be attached to this message. attrs['_meta'] = _MessageInfo( diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 4e20a551845b..5d35457510bd 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name='proto-plus', - version='0.2.0', + version='0.2.1', license='Apache 2.0', author='Luke Sneeringer', author_email='lukesneeringer@google.com', From ddc5d507673d19ce62b309e01c1b852bc2447c69 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Tue, 1 Jan 2019 18:50:45 -0800 Subject: [PATCH 012/272] [refactor] Add module-level `__protobuf__` value. (#14) This replaces the magic behavior attached to `__all__`, and the individual `Meta` inner classes. Note: This is a breaking change. --- packages/proto-plus/proto/__init__.py | 2 + packages/proto-plus/proto/message.py | 38 +++++++------- packages/proto-plus/proto/modules.py | 50 +++++++++++++++++++ .../tests/test_fields_composite_string_ref.py | 33 ++++++------ .../{test_message_all.py => test_modules.py} | 41 +++++++++++++-- 5 files changed, 126 insertions(+), 38 deletions(-) create mode 100644 packages/proto-plus/proto/modules.py rename packages/proto-plus/tests/{test_message_all.py => test_modules.py} (65%) diff --git a/packages/proto-plus/proto/__init__.py b/packages/proto-plus/proto/__init__.py index 577eac40f35c..0319199d8584 100644 --- a/packages/proto-plus/proto/__init__.py +++ b/packages/proto-plus/proto/__init__.py @@ -17,6 +17,7 @@ from .fields import RepeatedField from .marshal import Marshal from .message import Message +from .modules import define_module as module from .primitives import ProtoType @@ -45,6 +46,7 @@ 'RepeatedField', 'Marshal', 'Message', + 'module', # Expose the types directly. 'DOUBLE', diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 815bf88c8ac9..5ac26ae32659 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -17,6 +17,7 @@ import copy import inspect import re +import sys import uuid from typing import List, Type @@ -42,12 +43,16 @@ def __new__(mcls, name, bases, attrs): if not bases: return super().__new__(mcls, name, bases, attrs) + # Pull a reference to the module where this class is being + # declared. + module = sys.modules.get(attrs.get('__module__')) + # Pop metadata off the attrs. - Meta = attrs.pop('Meta', object()) + proto_module = getattr(module, '__protobuf__', object()) # A package and full name should be present. - package = getattr(Meta, 'package', '') - marshal = Marshal(name=getattr(Meta, 'marshal', package)) + package = getattr(proto_module, 'package', '') + marshal = Marshal(name=getattr(proto_module, 'marshal', package)) local_path = tuple(attrs.get('__qualname__', name).split('.')) # Sanity check: We get the wrong full name if a class is declared @@ -57,9 +62,7 @@ def __new__(mcls, name, bases, attrs): local_path = local_path[:ix - 1] + local_path[ix + 1:] # Determine the full name in protocol buffers. - full_name = getattr(Meta, 'full_name', - '.'.join((package,) + local_path).lstrip('.'), - ) + full_name = '.'.join((package,) + local_path).lstrip('.') # Special case: Maps. Map fields are special; they are essentially # shorthand for a nested message and a repeated field of that message. @@ -92,10 +95,7 @@ def __new__(mcls, name, bases, attrs): prefix=attrs.get('__qualname__', name), name=msg_name, ), - 'Meta': type('Meta', (object,), { - 'options': descriptor_pb2.MessageOptions(map_entry=True), - 'package': package, - }), + '_pb_options': {'map_entry': True}, }) entry_attrs['key'] = Field(field.map_key_type, number=1) entry_attrs['value'] = Field(field.proto_type, number=2, @@ -196,7 +196,7 @@ def __new__(mcls, name, bases, attrs): file_info.descriptor.dependency.append(proto_import) # Retrieve any message options. - opts = getattr(Meta, 'options', descriptor_pb2.MessageOptions()) + opts = descriptor_pb2.MessageOptions(**attrs.pop('_pb_options', {})) # Create the underlying proto descriptor. desc = descriptor_pb2.DescriptorProto( @@ -626,13 +626,17 @@ def ready(self, new_class): if field.message not in self.messages: return False - # If the module in which this class is defined provides an __all__, - # do not generate the file descriptor until every member of __all__ - # has been populated. + # If the module in which this class is defined provides a + # __protobuf__ property, it may have a manifest. + # + # Do not generate the file descriptor until every member of the + # manifest has been populated. module = inspect.getmodule(new_class) - manifest = set(getattr(module, '__all__', ())).difference( - {new_class.__name__}, - ) + manifest = frozenset() + if hasattr(module, '__protobuf__'): + manifest = module.__protobuf__.manifest.difference( + {new_class.__name__}, + ) if not all([hasattr(module, i) for i in manifest]): return False diff --git a/packages/proto-plus/proto/modules.py b/packages/proto-plus/proto/modules.py new file mode 100644 index 000000000000..a5ba94ffd42e --- /dev/null +++ b/packages/proto-plus/proto/modules.py @@ -0,0 +1,50 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Set +import collections + + +_ProtoModule = collections.namedtuple('ProtoModule', + ['package', 'marshal', 'manifest'], +) + + +def define_module(*, package: str, marshal: str = None, + manifest: Set[str] = frozenset()) -> _ProtoModule: + """Define a protocol buffers module. + + The settings defined here are used for all protobuf messages + declared in the module of the given name. + + Args: + package (str): The proto package name. + marshal (str): The name of the marshal to use. It is recommended + to use one marshal per Python library (e.g. package on PyPI). + manifest (Tuple[str]): A tuple of classes to be created. Setting + this adds a slight efficiency in piecing together proto + descriptors under the hood. + """ + if not marshal: + marshal = package + return _ProtoModule( + package=package, + marshal=marshal, + manifest=frozenset(manifest), + ) + + +__all__ = ( + 'define_module', +) diff --git a/packages/proto-plus/tests/test_fields_composite_string_ref.py b/packages/proto-plus/tests/test_fields_composite_string_ref.py index 865bc0da5f8a..f8df83441e20 100644 --- a/packages/proto-plus/tests/test_fields_composite_string_ref.py +++ b/packages/proto-plus/tests/test_fields_composite_string_ref.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + import proto @@ -31,24 +33,19 @@ class Foo(proto.Message): def test_composite_forward_ref_with_package(): - class Spam(proto.Message): - foo = proto.Field('Foo', number=1) - - class Meta: - package = 'abc.def' - - class Eggs(proto.Message): - foo = proto.Field('abc.def.Foo', number=1) - - class Meta: - package = 'abc.def' - - class Foo(proto.Message): - bar = proto.Field(proto.STRING, number=1) - baz = proto.Field(proto.INT64, number=2) - - class Meta: - package = 'abc.def' + sys.modules[__name__].__protobuf__ = proto.module(package='abc.def') + try: + class Spam(proto.Message): + foo = proto.Field('Foo', number=1) + + class Eggs(proto.Message): + foo = proto.Field('abc.def.Foo', number=1) + + class Foo(proto.Message): + bar = proto.Field(proto.STRING, number=1) + baz = proto.Field(proto.INT64, number=2) + finally: + del sys.modules[__name__].__protobuf__ spam = Spam(foo=Foo(bar='str', baz=42)) eggs = Eggs(foo=Foo(bar='rts', baz=24)) diff --git a/packages/proto-plus/tests/test_message_all.py b/packages/proto-plus/tests/test_modules.py similarity index 65% rename from packages/proto-plus/tests/test_message_all.py rename to packages/proto-plus/tests/test_modules.py index a2ee17985aa8..0c78eab81a13 100644 --- a/packages/proto-plus/tests/test_message_all.py +++ b/packages/proto-plus/tests/test_modules.py @@ -12,16 +12,51 @@ # See the License for the specific language governing permissions and # limitations under the License. -import inspect from unittest import mock +import inspect +import sys from google.protobuf import wrappers_pb2 import proto -def test_message_creation_all(): - __all__ = ('Foo', 'Bar', 'Baz') # noqa: F841 +def test_module_package(): + sys.modules[__name__].__protobuf__ = proto.module(package='spam.eggs.v1') + try: + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + marshal = proto.Marshal(name='spam.eggs.v1') + + assert Foo.meta.package == 'spam.eggs.v1' + assert Foo.pb() in marshal._rules + finally: + del sys.modules[__name__].__protobuf__ + + +def test_module_package_explicit_marshal(): + sys.modules[__name__].__protobuf__ = proto.module( + package='spam.eggs.v1', + marshal='foo', + ) + try: + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + marshal = proto.Marshal(name='foo') + + assert Foo.meta.package == 'spam.eggs.v1' + assert Foo.pb() in marshal._rules + finally: + del sys.modules[__name__].__protobuf__ + + +def test_module_manifest(): + __protobuf__ = proto.module( + manifest={'Foo', 'Bar', 'Baz'}, + package='spam.eggs.v1', + ) # We want to fake a module, but modules have attribute access, and # `frame.f_locals` is a dictionary. Since we only actually care about From ba40b1c325c79ac6c2c20713df2724d01b660452 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Tue, 1 Jan 2019 19:22:22 -0800 Subject: [PATCH 013/272] [chore] Version bump to 0.3.0. (#15) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 5d35457510bd..567b7b1352ae 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name='proto-plus', - version='0.2.1', + version='0.3.0', license='Apache 2.0', author='Luke Sneeringer', author_email='lukesneeringer@google.com', From f16c217f01c9b426c8aa7e02a47937e57597f7b2 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Sun, 3 Mar 2019 17:28:39 -0800 Subject: [PATCH 014/272] [chore] Fix a comment. (#16) --- packages/proto-plus/proto/message.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 5ac26ae32659..d5b793fe9d28 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -46,8 +46,6 @@ def __new__(mcls, name, bases, attrs): # Pull a reference to the module where this class is being # declared. module = sys.modules.get(attrs.get('__module__')) - - # Pop metadata off the attrs. proto_module = getattr(module, '__protobuf__', object()) # A package and full name should be present. From e5aefafe24b649b4298a4622642f032502c415bf Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Sun, 26 May 2019 13:22:15 -0700 Subject: [PATCH 015/272] [chore] Update to proper nox. (#17) --- packages/proto-plus/.circleci/config.yml | 14 +++++++------- packages/proto-plus/{nox.py => noxfile.py} | 0 2 files changed, 7 insertions(+), 7 deletions(-) rename packages/proto-plus/{nox.py => noxfile.py} (100%) diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index 8017c3a0f24c..df043690bbb6 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -55,7 +55,7 @@ jobs: - run: name: Install nox and codecov. command: | - pip install --pre nox-automation + pip install nox pip install codecov - run: name: Run unit tests. @@ -72,7 +72,7 @@ jobs: - run: name: Install nox and codecov. command: | - pip install --pre nox-automation + pip install nox pip install codecov - run: name: Run unit tests. @@ -89,7 +89,7 @@ jobs: - run: name: Install nox and codecov. command: | - pip install --pre nox-automation + pip install nox pip install codecov - run: name: Run unit tests. @@ -106,7 +106,7 @@ jobs: - run: name: Install nox and codecov. command: | - pip install --pre nox-automation + pip install nox pip install codecov - run: name: Run unit tests. @@ -123,7 +123,7 @@ jobs: - run: name: Install nox and codecov. command: | - pip install --pre nox-automation + pip install nox pip install codecov - run: name: Run unit tests. @@ -140,7 +140,7 @@ jobs: - run: name: Install nox and codecov. command: | - pip install --pre nox-automation + pip install nox pip install codecov - run: name: Run unit tests. @@ -156,7 +156,7 @@ jobs: - checkout - run: name: Install nox. - command: pip install --pre nox-automation + command: pip install nox - run: name: Build the documentation. command: nox -s docs diff --git a/packages/proto-plus/nox.py b/packages/proto-plus/noxfile.py similarity index 100% rename from packages/proto-plus/nox.py rename to packages/proto-plus/noxfile.py From f42669e3af165cc231baf9d6a4379909900f5605 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Sun, 26 May 2019 14:45:19 -0700 Subject: [PATCH 016/272] [refactor] Move code around; no-op change. (#18) This sets us up for implementing better enum support. --- packages/proto-plus/proto/_descriptor_info.py | 53 ++++++ packages/proto-plus/proto/_file_info.py | 137 +++++++++++++++ packages/proto-plus/proto/message.py | 157 ++---------------- packages/proto-plus/tests/conftest.py | 2 +- 4 files changed, 207 insertions(+), 142 deletions(-) create mode 100644 packages/proto-plus/proto/_descriptor_info.py create mode 100644 packages/proto-plus/proto/_file_info.py diff --git a/packages/proto-plus/proto/_descriptor_info.py b/packages/proto-plus/proto/_descriptor_info.py new file mode 100644 index 000000000000..f2a64c709210 --- /dev/null +++ b/packages/proto-plus/proto/_descriptor_info.py @@ -0,0 +1,53 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from proto.marshal import Marshal + + +def compile(name, attrs): + """Build and return a ``_DescriptorInfo`` object. + + Args: + name (str): The name of the new class, as sent to ``type.__new__``. + attrs (Mapping[str, Any]): The attrs for a new class, as sent + to ``type.__new__`` + + Returns: + Tuple[Tuple[str], str, ~.Marshal]: + - The local path of the proto component. + - The proto package, if any (empty string otherwise). + - The marshal object to use. + """ + # Pull a reference to the module where this class is being + # declared. + module = sys.modules.get(attrs.get('__module__')) + proto_module = getattr(module, '__protobuf__', object()) + + # A package should be present; get the marshal from there. + package = getattr(proto_module, 'package', '') + marshal = Marshal(name=getattr(proto_module, 'marshal', package)) + + # Determine the local path of this proto component within the file. + local_path = tuple(attrs.get('__qualname__', name).split('.')) + + # Sanity check: We get the wrong full name if a class is declared + # inside a function local scope; correct this. + if '' in local_path: + ix = local_path.index('') + local_path = local_path[:ix - 1] + local_path[ix + 1:] + + # Done; return the data. + return (local_path, package, marshal) diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py new file mode 100644 index 000000000000..68dd8274d188 --- /dev/null +++ b/packages/proto-plus/proto/_file_info.py @@ -0,0 +1,137 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections +import collections.abc +import inspect +import uuid + +from google.protobuf import descriptor_pool +from google.protobuf import message +from google.protobuf import reflection + +from proto.marshal.rules.message import MessageRule + + +class _FileInfo(collections.namedtuple( + '_FileInfo', ['descriptor', 'messages', 'name', 'nested'])): + registry = {} # Mapping[str, '_FileInfo'] + + def generate_file_pb(self): + """Generate the descriptors for all protos in the file. + + This method takes the file descriptor attached to the parent + message and generates the immutable descriptors for all of the + messages in the file descriptor. (This must be done in one fell + swoop for immutability and to resolve proto cross-referencing.) + + This is run automatically when the last proto in the file is + generated, as determined by the module's __all__ tuple. + """ + pool = descriptor_pool.Default() + + # Salt the filename in the descriptor. + # This allows re-use of the filename by other proto messages if + # needed (e.g. if __all__ is not used). + self.descriptor.name = '{prefix}_{salt}.proto'.format( + prefix=self.descriptor.name[:-6], + salt=str(uuid.uuid4())[0:8], + ) + + # Add the file descriptor. + pool.Add(self.descriptor) + + # Adding the file descriptor to the pool created a descriptor for + # each message; go back through our wrapper messages and associate + # them with the internal protobuf version. + for full_name, proto_plus_message in self.messages.items(): + # Get the descriptor from the pool, and create the protobuf + # message based on it. + descriptor = pool.FindMessageTypeByName(full_name) + pb_message = reflection.GeneratedProtocolMessageType( + descriptor.name, + (message.Message,), + {'DESCRIPTOR': descriptor, '__module__': None}, + ) + + # Register the message with the marshal so it is wrapped + # appropriately. + proto_plus_message._meta._pb = pb_message + proto_plus_message._meta.marshal.register( + pb_message, + MessageRule(pb_message, proto_plus_message) + ) + + # Iterate over any fields on the message and, if their type + # is a message still referenced as a string, resolve the reference. + for field in proto_plus_message._meta.fields.values(): + if field.message and isinstance(field.message, str): + field.message = self.messages[field.message] + + # We no longer need to track this file's info; remove it from + # the module's registry and from this object. + self.registry.pop(self.name) + + def ready(self, new_class): + """Return True if a file descriptor may added, False otherwise. + + This determine if all the messages that we plan to create have been + created, as best as we are able. + + Since messages depend on one another, we create descriptor protos + (which reference each other using strings) and wait until we have + built everything that is going to be in the module, and then + use the descriptor protos to instantiate the actual descriptors in + one fell swoop. + + Args: + new_class (~.MessageMeta): The new class currently undergoing + creation. + """ + # If there are any nested descriptors that have not been assigned to + # the descriptors that should contain them, then we are not ready. + if len(self.nested): + return False + + # If there are any unresolved fields (fields with a composite message + # declared as a string), ensure that the corresponding message is + # declared. + for field in self.unresolved_fields: + if field.message not in self.messages: + return False + + # If the module in which this class is defined provides a + # __protobuf__ property, it may have a manifest. + # + # Do not generate the file descriptor until every member of the + # manifest has been populated. + module = inspect.getmodule(new_class) + manifest = frozenset() + if hasattr(module, '__protobuf__'): + manifest = module.__protobuf__.manifest.difference( + {new_class.__name__}, + ) + if not all([hasattr(module, i) for i in manifest]): + return False + + # Okay, we are ready. + return True + + @property + def unresolved_fields(self): + """Return fields with referencing message types as strings.""" + for proto_plus_message in self.messages.values(): + for field in proto_plus_message._meta.fields.values(): + if field.message and isinstance(field.message, str): + yield field diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index d5b793fe9d28..bafa3c512899 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -15,23 +15,20 @@ import collections import collections.abc import copy -import inspect import re import sys -import uuid from typing import List, Type from google.protobuf import descriptor_pb2 -from google.protobuf import descriptor_pool from google.protobuf import message -from google.protobuf import reflection from google.protobuf import symbol_database +from proto import _descriptor_info +from proto import _file_info from proto.fields import Field from proto.fields import MapField from proto.fields import RepeatedField from proto.marshal import Marshal -from proto.marshal.rules.message import MessageRule from proto.primitives import ProtoType @@ -43,21 +40,9 @@ def __new__(mcls, name, bases, attrs): if not bases: return super().__new__(mcls, name, bases, attrs) - # Pull a reference to the module where this class is being - # declared. - module = sys.modules.get(attrs.get('__module__')) - proto_module = getattr(module, '__protobuf__', object()) - - # A package and full name should be present. - package = getattr(proto_module, 'package', '') - marshal = Marshal(name=getattr(proto_module, 'marshal', package)) - local_path = tuple(attrs.get('__qualname__', name).split('.')) - - # Sanity check: We get the wrong full name if a class is declared - # inside a function local scope; correct this. - if '' in local_path: - ix = local_path.index('') - local_path = local_path[:ix - 1] + local_path[ix + 1:] + # Get the essential information about the proto package, and where + # this component belongs within the file. + local_path, package, marshal = _descriptor_info.compile(name, attrs) # Determine the full name in protocol buffers. full_name = '.'.join((package,) + local_path).lstrip('.') @@ -176,16 +161,19 @@ def __new__(mcls, name, bases, attrs): # Get or create the information about the file, including the # descriptor to which the new message descriptor shall be added. - file_info = _FileInfo.registry.setdefault(filename, _FileInfo( - descriptor=descriptor_pb2.FileDescriptorProto( + file_info = _file_info._FileInfo.registry.setdefault( + filename, + _file_info._FileInfo( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, + package=package, + syntax='proto3', + ), + messages=collections.OrderedDict(), name=filename, - package=package, - syntax='proto3', + nested={}, ), - messages=collections.OrderedDict(), - name=filename, - nested={}, - )) + ) # Ensure any imports that would be necessary are assigned to the file # descriptor proto being created. @@ -537,119 +525,6 @@ def pb(self) -> Type[message.Message]: return self._pb -class _FileInfo(collections.namedtuple( - '_FileInfo', ['descriptor', 'messages', 'name', 'nested'])): - registry = {} # Mapping[str, '_FileInfo'] - - def generate_file_pb(self): - """Generate the descriptors for all protos in the file. - - This method takes the file descriptor attached to the parent - message and generates the immutable descriptors for all of the - messages in the file descriptor. (This must be done in one fell - swoop for immutability and to resolve proto cross-referencing.) - - This is run automatically when the last proto in the file is - generated, as determined by the module's __all__ tuple. - """ - pool = descriptor_pool.Default() - - # Salt the filename in the descriptor. - # This allows re-use of the filename by other proto messages if - # needed (e.g. if __all__ is not used). - self.descriptor.name = '{prefix}_{salt}.proto'.format( - prefix=self.descriptor.name[:-6], - salt=str(uuid.uuid4())[0:8], - ) - - # Add the file descriptor. - pool.Add(self.descriptor) - - # Adding the file descriptor to the pool created a descriptor for - # each message; go back through our wrapper messages and associate - # them with the internal protobuf version. - for full_name, proto_plus_message in self.messages.items(): - # Get the descriptor from the pool, and create the protobuf - # message based on it. - descriptor = pool.FindMessageTypeByName(full_name) - pb_message = reflection.GeneratedProtocolMessageType( - descriptor.name, - (message.Message,), - {'DESCRIPTOR': descriptor, '__module__': None}, - ) - - # Register the message with the marshal so it is wrapped - # appropriately. - proto_plus_message._meta._pb = pb_message - proto_plus_message._meta.marshal.register( - pb_message, - MessageRule(pb_message, proto_plus_message) - ) - - # Iterate over any fields on the message and, if their type - # is a message still referenced as a string, resolve the reference. - for field in proto_plus_message._meta.fields.values(): - if field.message and isinstance(field.message, str): - field.message = self.messages[field.message] - - # We no longer need to track this file's info; remove it from - # the module's registry and from this object. - self.registry.pop(self.name) - - def ready(self, new_class): - """Return True if a file descriptor may added, False otherwise. - - This determine if all the messages that we plan to create have been - created, as best as we are able. - - Since messages depend on one another, we create descriptor protos - (which reference each other using strings) and wait until we have - built everything that is going to be in the module, and then - use the descriptor protos to instantiate the actual descriptors in - one fell swoop. - - Args: - new_class (~.MessageMeta): The new class currently undergoing - creation. - """ - # If there are any nested descriptors that have not been assigned to - # the descriptors that should contain them, then we are not ready. - if len(self.nested): - return False - - # If there are any unresolved fields (fields with a composite message - # declared as a string), ensure that the corresponding message is - # declared. - for field in self.unresolved_fields: - if field.message not in self.messages: - return False - - # If the module in which this class is defined provides a - # __protobuf__ property, it may have a manifest. - # - # Do not generate the file descriptor until every member of the - # manifest has been populated. - module = inspect.getmodule(new_class) - manifest = frozenset() - if hasattr(module, '__protobuf__'): - manifest = module.__protobuf__.manifest.difference( - {new_class.__name__}, - ) - if not all([hasattr(module, i) for i in manifest]): - return False - - # Okay, we are ready. - return True - - @property - def unresolved_fields(self): - """Return fields with referencing message types as strings.""" - for proto_plus_message in self.messages.values(): - for field in proto_plus_message._meta.fields.values(): - if field.message and isinstance(field.message, str): - yield field - - __all__ = ( 'Message', ) diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index f32ef5773048..a2d577408f9f 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -20,9 +20,9 @@ from google.protobuf import reflection from google.protobuf import symbol_database +from proto._file_info import _FileInfo from proto.marshal import Marshal from proto.marshal import rules -from proto.message import _FileInfo def pytest_runtest_setup(item): From c4018507c7fe57c367fac2cadf6e56455c7aaf47 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Mon, 27 May 2019 17:20:06 -0700 Subject: [PATCH 017/272] [feat] Enums (#19) Greatly improves enum support, now returning enum objects instead of plain ints, and accepting strings. --- packages/proto-plus/docs/messages.rst | 56 +++++++++++ .../proto-plus/docs/reference/message.rst | 4 + packages/proto-plus/docs/status.rst | 3 - packages/proto-plus/proto/__init__.py | 2 + packages/proto-plus/proto/_file_info.py | 6 +- .../{_descriptor_info.py => _package_info.py} | 16 +--- packages/proto-plus/proto/enums.py | 44 +++++++++ packages/proto-plus/proto/fields.py | 38 ++++++-- packages/proto-plus/proto/marshal/marshal.py | 7 +- .../proto-plus/proto/marshal/rules/enums.py | 57 +++++++++++ packages/proto-plus/proto/message.py | 18 +++- packages/proto-plus/tests/test_fields_enum.py | 94 +++++++++++++++---- .../tests/test_marshal_types_enum.py | 58 ++++++++++++ 13 files changed, 353 insertions(+), 50 deletions(-) rename packages/proto-plus/proto/{_descriptor_info.py => _package_info.py} (69%) create mode 100644 packages/proto-plus/proto/enums.py create mode 100644 packages/proto-plus/proto/marshal/rules/enums.py create mode 100644 packages/proto-plus/tests/test_marshal_types_enum.py diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index 24aa6d5d9c32..d7a58ccf0d6e 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -78,6 +78,62 @@ Instantiate messages using either keyword arguments or a :class:`dict` >>> song.title 'Canon in D' +Enums +----- + +Enums are also supported: + +.. code-block:: python + + import proto + + class Genre(proto.Enum): + GENRE_UNSPECIFIED = 0 + CLASSICAL = 1 + JAZZ = 2 + ROCK = 3 + + class Composer(proto.Message): + given_name = proto.Field(proto.STRING, number=1) + family_name = proto.Field(proto.STRING, number=2) + + class Song(proto.Message): + composer = proto.Field(Composer, number=1) + title = proto.Field(proto.STRING, number=2) + lyrics = proto.Field(proto.STRING, number=3) + year = proto.Field(proto.INT32, number=4) + genre = proto.Field(Genre, number=5) + +All enums **must** begin with a ``0`` value, which is always the default in +proto3 (and, as above, indistuiguishable from unset). + +Enums utilize Python :class:`enum.IntEnum` under the hood: + +.. code-block:: python + + >>> song = Song( + ... composer={'given_name': 'Johann', 'family_name': 'Pachelbel'}, + ... title='Canon in D', + ... year=1680, + ... genre=Genre.CLASSICAL, + ... ) + >>> song.genre + + >>> song.genre.name + 'CLASSICAL' + >>> song.genre.value + 1 + +Additionally, it is possible to provide strings or plain integers: + +.. code-block:: python + + >>> song.genre = 2 + >>> song.genre + + >>> song.genre = 'CLASSICAL' + + Serialization ------------- diff --git a/packages/proto-plus/docs/reference/message.rst b/packages/proto-plus/docs/reference/message.rst index 4fe8273fd023..1d9e070c1f6c 100644 --- a/packages/proto-plus/docs/reference/message.rst +++ b/packages/proto-plus/docs/reference/message.rst @@ -11,3 +11,7 @@ Message and Field .. automodule:: proto.fields :members: + + +.. automodule:: proto.enums + :members: diff --git a/packages/proto-plus/docs/status.rst b/packages/proto-plus/docs/status.rst index 9fefa35550c2..6004b127ed76 100644 --- a/packages/proto-plus/docs/status.rst +++ b/packages/proto-plus/docs/status.rst @@ -14,7 +14,4 @@ Nice things this library does: Upcoming work ------------- -- Improved enum support. -- Specialized behavior for ``google.protobuf.Value`` and - ``google.protobuf.Struct`` objects. - Specialized behavior for ``google.protobuf.FieldMask`` objects. diff --git a/packages/proto-plus/proto/__init__.py b/packages/proto-plus/proto/__init__.py index 0319199d8584..5cf75cdd0c2d 100644 --- a/packages/proto-plus/proto/__init__.py +++ b/packages/proto-plus/proto/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from .enums import Enum from .fields import Field from .fields import MapField from .fields import RepeatedField @@ -41,6 +42,7 @@ __all__ = ( + 'Enum', 'Field', 'MapField', 'RepeatedField', diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 68dd8274d188..b50526c0b597 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -25,7 +25,7 @@ class _FileInfo(collections.namedtuple( - '_FileInfo', ['descriptor', 'messages', 'name', 'nested'])): + '_FileInfo', ['descriptor', 'messages', 'enums', 'name', 'nested'])): registry = {} # Mapping[str, '_FileInfo'] def generate_file_pb(self): @@ -67,6 +67,10 @@ def generate_file_pb(self): # Register the message with the marshal so it is wrapped # appropriately. + # + # We do this here (rather than at class creation) because it + # is not until this point that we have an actual protobuf + # message subclass, which is what we need to use. proto_plus_message._meta._pb = pb_message proto_plus_message._meta.marshal.register( pb_message, diff --git a/packages/proto-plus/proto/_descriptor_info.py b/packages/proto-plus/proto/_package_info.py similarity index 69% rename from packages/proto-plus/proto/_descriptor_info.py rename to packages/proto-plus/proto/_package_info.py index f2a64c709210..b0265dc2cb0e 100644 --- a/packages/proto-plus/proto/_descriptor_info.py +++ b/packages/proto-plus/proto/_package_info.py @@ -18,7 +18,7 @@ def compile(name, attrs): - """Build and return a ``_DescriptorInfo`` object. + """Return the package and marshal to use. Args: name (str): The name of the new class, as sent to ``type.__new__``. @@ -26,8 +26,7 @@ def compile(name, attrs): to ``type.__new__`` Returns: - Tuple[Tuple[str], str, ~.Marshal]: - - The local path of the proto component. + Tuple[str, ~.Marshal]: - The proto package, if any (empty string otherwise). - The marshal object to use. """ @@ -40,14 +39,5 @@ def compile(name, attrs): package = getattr(proto_module, 'package', '') marshal = Marshal(name=getattr(proto_module, 'marshal', package)) - # Determine the local path of this proto component within the file. - local_path = tuple(attrs.get('__qualname__', name).split('.')) - - # Sanity check: We get the wrong full name if a class is declared - # inside a function local scope; correct this. - if '' in local_path: - ix = local_path.index('') - local_path = local_path[:ix - 1] + local_path[ix + 1:] - # Done; return the data. - return (local_path, package, marshal) + return (package, marshal) diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py new file mode 100644 index 000000000000..b02843186faa --- /dev/null +++ b/packages/proto-plus/proto/enums.py @@ -0,0 +1,44 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import enum + +from proto import _package_info +from proto.marshal.rules.enums import EnumRule + + +class ProtoEnumMeta(enum.EnumMeta): + """A metaclass for building and registering protobuf enums.""" + def __new__(mcls, name, bases, attrs): + # Do not do any special behavior for `proto.Enum` itself. + if bases[0] == enum.IntEnum: + return super().__new__(mcls, name, bases, attrs) + + # Get the essential information about the proto package, and where + # this component belongs within the file. + package, marshal = _package_info.compile(name, attrs) + + # Run the superclass constructor. + cls = super().__new__(mcls, name, bases, attrs) + + # Register the enum with the marshal. + marshal.register(cls, EnumRule(cls)) + + # Done; return the class. + return cls + + +class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): + """A enum object that also builds a protobuf enum descriptor.""" + pass diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index 097640cc935e..fb33f7bfde5f 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from enum import EnumMeta + from google.protobuf import descriptor_pb2 from proto.primitives import ProtoType @@ -31,8 +33,14 @@ def __init__(self, proto_type, *, number: int, # If the proto type sent is an object or a string, it is really # a message or enum. if not isinstance(proto_type, int): - message = proto_type - proto_type = ProtoType.MESSAGE + # Note: We only support the "shortcut syntax" for enums + # when receiving the actual class. + if isinstance(proto_type, EnumMeta): + enum = proto_type + proto_type = ProtoType.ENUM + else: + message = proto_type + proto_type = ProtoType.MESSAGE # Save the direct arguments. self.number = number @@ -55,6 +63,7 @@ def __init__(self, proto_type, *, number: int, @property def descriptor(self): """Return the descriptor for the field.""" + proto_type = self.proto_type if not self._descriptor: # Resolve the message type, if any, to a string. type_name = None @@ -71,17 +80,26 @@ def descriptor(self): else: type_name = self.message.meta.full_name elif self.enum: - # FIXME: This is obviously wrong (however, it does *work*). - # We need to set the enum type name and add the enum to - # the descriptor (like with messages above). - self.proto_type = ProtoType.INT32 + # Nos decipiat. + # + # As far as the wire format is concerned, enums are int32s. + # Protocol buffers itself also only sends ints; the enum + # objects are simply helper classes for translating names + # and values and it is the user's job to resolve to an int. + # + # Therefore, the non-trivial effort of adding the actual + # enum descriptors seems to add little or no actual value. + # + # FIXME: Eventually, come back and put in the actual enum + # descriptors. + proto_type = ProtoType.INT32 # Set the descriptor. self._descriptor = descriptor_pb2.FieldDescriptorProto( name=self.name, number=self.number, label=3 if self.repeated else 1, - type=self.proto_type, + type=proto_type, type_name=type_name, json_name=self.json_name, ) @@ -102,7 +120,11 @@ def package(self) -> str: @property def pb_type(self): """Return the composite type of the field, or None for primitives.""" - # For primitives, return None. + # For enums, return the Python enum. + if self.enum: + return self.enum + + # For non-enum primitives, return None. if not self.message: return None diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index 24a0df428086..e0983ee0376d 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -13,6 +13,7 @@ # limitations under the License. import abc +import enum from google.protobuf import message from google.protobuf import duration_pb2 @@ -85,9 +86,9 @@ class TimestampRule: """ # Sanity check: Do not register anything to a class that is not # a protocol buffer message. - if not issubclass(proto_type, message.Message): - raise TypeError('Only protocol buffer messages may be registered ' - 'to the marshal.') + if not issubclass(proto_type, (message.Message, enum.IntEnum)): + raise TypeError('Only enums and protocol buffer messages may be ' + 'registered to the marshal.') # If a rule was provided, register it and be done. if rule: diff --git a/packages/proto-plus/proto/marshal/rules/enums.py b/packages/proto-plus/proto/marshal/rules/enums.py new file mode 100644 index 000000000000..7ef2f160d82a --- /dev/null +++ b/packages/proto-plus/proto/marshal/rules/enums.py @@ -0,0 +1,57 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Type +import enum +import warnings + + +class EnumRule: + """A marshal for converting between integer values and enum values.""" + + def __init__(self, enum_class: Type[enum.IntEnum]): + self._enum = enum_class + + def to_python(self, value, *, absent: bool = None): + if isinstance(value, int) and not isinstance(value, self._enum): + try: + # Coerce the int on the wire to the enum value. + return self._enum(value) + except ValueError: + # Since it is possible to add values to enums, we do + # not want to flatly error on this. + # + # However, it is useful to make some noise about it so + # the user realizes that an unexpected value came along. + warnings.warn('Unrecognized {name} enum value: {value}'.format( + name=self._enum.__name__, + value=value, + )) + return value + + def to_proto(self, value): + # Accept enum values and coerce to the pure integer. + # This is not strictly necessary (protocol buffers can take these + # objects as they subclass int) but nevertheless seems like the + # right thing to do. + if isinstance(value, self._enum): + return value.value + + # If a string is provided that matches an enum value, coerce it + # to the enum value. + if isinstance(value, str): + return self._enum[value].value + + # We got a pure integer; pass it on. + return value diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index bafa3c512899..36eeee1ebd5d 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -16,15 +16,14 @@ import collections.abc import copy import re -import sys from typing import List, Type from google.protobuf import descriptor_pb2 from google.protobuf import message from google.protobuf import symbol_database -from proto import _descriptor_info from proto import _file_info +from proto import _package_info from proto.fields import Field from proto.fields import MapField from proto.fields import RepeatedField @@ -42,7 +41,16 @@ def __new__(mcls, name, bases, attrs): # Get the essential information about the proto package, and where # this component belongs within the file. - local_path, package, marshal = _descriptor_info.compile(name, attrs) + package, marshal = _package_info.compile(name, attrs) + + # Determine the local path of this proto component within the file. + local_path = tuple(attrs.get('__qualname__', name).split('.')) + + # Sanity check: We get the wrong full name if a class is declared + # inside a function local scope; correct this. + if '' in local_path: + ix = local_path.index('') + local_path = local_path[:ix - 1] + local_path[ix + 1:] # Determine the full name in protocol buffers. full_name = '.'.join((package,) + local_path).lstrip('.') @@ -153,8 +161,7 @@ def __new__(mcls, name, bases, attrs): # Determine the filename. # We determine an appropriate proto filename based on the - # Python module. If the filename has already been used (which would - # cause collisions in the descriptor pool), we salt it. + # Python module. filename = '{0}.proto'.format( attrs.get('__module__', name.lower()).replace('.', '/') ) @@ -169,6 +176,7 @@ def __new__(mcls, name, bases, attrs): package=package, syntax='proto3', ), + enums=collections.OrderedDict(), messages=collections.OrderedDict(), name=filename, nested={}, diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index a3490147244a..21c79d218197 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -12,64 +12,101 @@ # See the License for the specific language governing permissions and # limitations under the License. -import enum - import proto def test_outer_enum_init(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) + color = proto.Field(Color, number=1) foo = Foo(color=Color.RED) assert foo.color == Color.RED assert foo.color == 1 assert foo.color + assert isinstance(foo.color, Color) assert Foo.pb(foo).color == 1 def test_outer_enum_init_int(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) + color = proto.Field(Color, number=1) foo = Foo(color=1) assert foo.color == Color.RED assert foo.color == 1 assert foo.color + assert isinstance(foo.color, Color) + assert Foo.pb(foo).color == 1 + + +def test_outer_enum_init_str(): + class Color(proto.Enum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(Color, number=1) + + foo = Foo(color='RED') + assert foo.color == Color.RED + assert foo.color == 1 + assert foo.color + assert isinstance(foo.color, Color) assert Foo.pb(foo).color == 1 def test_outer_enum_init_dict(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) + color = proto.Field(Color, number=1) foo = Foo({'color': 1}) assert foo.color == Color.RED assert foo.color == 1 assert foo.color + assert isinstance(foo.color, Color) assert Foo.pb(foo).color == 1 +def test_outer_enum_init_dict_str(): + class Color(proto.Enum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(Color, number=1) + + foo = Foo({'color': 'BLUE'}) + assert foo.color == Color.BLUE + assert foo.color == 3 + assert foo.color + assert isinstance(foo.color, Color) + assert Foo.pb(foo).color == 3 + + def test_outer_enum_init_pb2(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 @@ -82,11 +119,12 @@ class Foo(proto.Message): assert foo.color == Color.RED assert foo.color == 1 assert foo.color + assert isinstance(foo.color, Color) assert Foo.pb(foo).color == 1 def test_outer_enum_unset(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 @@ -105,7 +143,7 @@ class Foo(proto.Message): def test_outer_enum_write(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 @@ -123,7 +161,7 @@ class Foo(proto.Message): def test_outer_enum_write_int(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 @@ -136,13 +174,33 @@ class Foo(proto.Message): foo.color = 3 assert foo.color == Color.BLUE assert foo.color == 3 + assert isinstance(foo.color, Color) + assert Foo.pb(foo).color == 3 + assert foo.color + + +def test_outer_enum_write_str(): + class Color(proto.Enum): + COLOR_UNSPECIFIED = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + class Foo(proto.Message): + color = proto.Field(Color, number=1) + + foo = Foo() + foo.color = 'BLUE' + assert foo.color == Color.BLUE + assert foo.color == 3 + assert isinstance(foo.color, Color) assert Foo.pb(foo).color == 3 assert foo.color def test_inner_enum_init(): class Foo(proto.Message): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 @@ -159,36 +217,38 @@ class Color(enum.IntEnum): def test_inner_enum_write(): class Foo(proto.Message): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - color = proto.Field(proto.ENUM, number=1, enum=Color) + color = proto.Field(Color, number=1) foo = Foo() foo.color = Foo.Color.GREEN assert foo.color == Foo.Color.GREEN assert foo.color == 2 + assert isinstance(foo.color, Foo.Color) assert Foo.pb(foo).color == 2 assert foo.color def test_enum_del(): - class Color(enum.IntEnum): + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) + color = proto.Field(Color, number=1) foo = Foo(color=Color.BLUE) del foo.color assert foo.color == Color.COLOR_UNSPECIFIED assert foo.color == 0 + assert isinstance(foo.color, Color) assert 'color' not in foo assert not foo.color assert Foo.pb(foo).color == 0 diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py new file mode 100644 index 000000000000..7127b579b83d --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -0,0 +1,58 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock +import warnings + +import proto +from proto.marshal.rules.enums import EnumRule + + +def test_to_proto(): + class Foo(proto.Enum): + FOO_UNSPECIFIED = 0 + BAR = 1 + BAZ = 2 + + enum_rule = EnumRule(Foo) + foo_a = enum_rule.to_proto(Foo.BAR) + foo_b = enum_rule.to_proto(1) + foo_c = enum_rule.to_proto('BAR') + # We want to distinguish literal `1` from `Foo.BAR` here + # (they are equivalent but not identical). + assert foo_a is foo_b is foo_c is 1 # noqa: F632 + + +def test_to_python(): + class Foo(proto.Enum): + FOO_UNSPECIFIED = 0 + BAR = 1 + BAZ = 2 + + enum_rule = EnumRule(Foo) + foo_a = enum_rule.to_python(1) + foo_b = enum_rule.to_python(Foo.BAR) + assert foo_a is foo_b is Foo.BAR + + +def test_to_python_unknown_value(): + class Foo(proto.Enum): + FOO_UNSPECIFIED = 0 + BAR = 1 + BAZ = 2 + + enum_rule = EnumRule(Foo) + with mock.patch.object(warnings, 'warn') as warn: + assert enum_rule.to_python(4) == 4 + warn.assert_called_once_with('Unrecognized Foo enum value: 4') From ecc2cc1fda6335c4ce1ef6becca7fc4552c79ff8 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Mon, 27 May 2019 17:23:24 -0700 Subject: [PATCH 018/272] [chore] Version bump to 0.4.0, beta. (#20) --- packages/proto-plus/README.rst | 2 +- packages/proto-plus/setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 7fade7cba435..b8f0bdbabd58 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -27,7 +27,7 @@ Disclaimer This is not an official Google product. -.. |release level| image:: https://img.shields.io/badge/release%20level-alpha-red.svg?style=flat +.. |release level| image:: https://img.shields.io/badge/release%20level-beta-yellow.svg?style=flat :target: https://cloud.google.com/terms/launch-stages .. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest :target: https://proto-plus-python.readthedocs.io/en/latest/ diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 567b7b1352ae..75aed8a74587 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name='proto-plus', - version='0.3.0', + version='0.4.0', license='Apache 2.0', author='Luke Sneeringer', author_email='lukesneeringer@google.com', @@ -39,7 +39,7 @@ 'protobuf >= 3.5.1', ), classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', From 3b95d6b46a6965ce84e2b869b1a5f27033cd18a8 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 13 Nov 2019 23:27:40 +0100 Subject: [PATCH 019/272] Add renovate.json (#21) --- packages/proto-plus/renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/proto-plus/renovate.json diff --git a/packages/proto-plus/renovate.json b/packages/proto-plus/renovate.json new file mode 100644 index 000000000000..f45d8f110c30 --- /dev/null +++ b/packages/proto-plus/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} From 9aaf20e16fe23f3683ad3926dfd82fa0a75b6afa Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 25 Mar 2020 11:29:39 -0700 Subject: [PATCH 020/272] Reference MutableMapping and MutableSequence from the proper abc submodule (#25) --- packages/proto-plus/proto/marshal/collections/maps.py | 2 +- packages/proto-plus/proto/marshal/collections/repeated.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/marshal/collections/maps.py b/packages/proto-plus/proto/marshal/collections/maps.py index e98899a98cdc..5fced6f0a974 100644 --- a/packages/proto-plus/proto/marshal/collections/maps.py +++ b/packages/proto-plus/proto/marshal/collections/maps.py @@ -17,7 +17,7 @@ from proto.utils import cached_property -class MapComposite(collections.MutableMapping): +class MapComposite(collections.abc.MutableMapping): """A view around a mutable sequence in protocol buffers. This implements the full Python MutableMapping interface, but all methods diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index c1df7a770670..796d39d62964 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -18,12 +18,13 @@ from proto.utils import cached_property -class Repeated(collections.MutableSequence): +class Repeated(collections.abc.MutableSequence): """A view around a mutable sequence in protocol buffers. This implements the full Python MutableSequence interface, but all methods modify the underlying field container directly. """ + def __init__(self, sequence, *, marshal): """Initialize a wrapper around a protobuf repeated field. From 3208a7a279b605dfe45b753d34a8db57a09c528f Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 27 Mar 2020 10:42:39 -0700 Subject: [PATCH 021/272] Change ownership (#26) --- packages/proto-plus/README.rst | 7 ------- packages/proto-plus/setup.py | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index b8f0bdbabd58..84f0436914c4 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -20,13 +20,6 @@ Documentation .. _documentation: https://proto-plus-python.readthedocs.io/en/latest/ - -Disclaimer ----------- - -This is not an official Google product. - - .. |release level| image:: https://img.shields.io/badge/release%20level-beta-yellow.svg?style=flat :target: https://cloud.google.com/terms/launch-stages .. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 75aed8a74587..7d00c834a666 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -27,8 +27,8 @@ name='proto-plus', version='0.4.0', license='Apache 2.0', - author='Luke Sneeringer', - author_email='lukesneeringer@google.com', + author='Dov Shlachter', + author_email='dovs@google.com', url='https://github.com/googleapis/proto-plus-python.git', packages=find_packages(exclude=['docs', 'tests']), description='Beautiful, Pythonic protocol buffers.', From 473db344447487f24ba84d2f00d4a3af7614574d Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 13 May 2020 10:43:33 -0700 Subject: [PATCH 022/272] Remove a warning about production merit (#27) --- packages/proto-plus/docs/index.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/proto-plus/docs/index.rst b/packages/proto-plus/docs/index.rst index 7366bb1098cf..0225095ebe4e 100644 --- a/packages/proto-plus/docs/index.rst +++ b/packages/proto-plus/docs/index.rst @@ -8,12 +8,6 @@ declaraing messages in `protocol buffers`_. It provides a wrapper around the official implementation, so that using messages feels natural while retaining the power and flexibility of protocol buffers. -.. warning:: - - This tool is a proof of concept and is being iterated on rapidly. - Feedback is welcome, but please do not try to use this in some kind of - system where stability is an expectation. - .. _protocol buffers: https://developers.google.com/protocol-buffers/ From 460cab44d76eb314401a3a5fd6578fbdf71e4cb5 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 12 Jun 2020 14:56:58 -0700 Subject: [PATCH 023/272] Bump version to 1.0.0 (#29) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 7d00c834a666..81584ce3fa98 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name='proto-plus', - version='0.4.0', + version='1.0.0', license='Apache 2.0', author='Dov Shlachter', author_email='dovs@google.com', From de8e53cb43be9a15f641e5e95dac6cf9895aa58a Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Sun, 12 Jul 2020 16:44:45 -0700 Subject: [PATCH 024/272] chore: update status in README and classifiers (#30) --- packages/proto-plus/README.rst | 5 +++-- packages/proto-plus/setup.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 84f0436914c4..7b634511d469 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -1,7 +1,7 @@ Proto Plus for Python ===================== -|release level| |ci| |docs| |codecov| +|pypi| |release level| |ci| |docs| |codecov| Beautiful, Pythonic protocol buffers. @@ -20,7 +20,8 @@ Documentation .. _documentation: https://proto-plus-python.readthedocs.io/en/latest/ -.. |release level| image:: https://img.shields.io/badge/release%20level-beta-yellow.svg?style=flat +.. |pypi| image:: https://img.shields.io/pypi/v/proto-plus.svg +.. |release level| image:: https://img.shields.io/badge/release%20level-ga-gold.svg?style=flat :target: https://cloud.google.com/terms/launch-stages .. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest :target: https://proto-plus-python.readthedocs.io/en/latest/ diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 81584ce3fa98..c816e5e3564a 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -39,7 +39,7 @@ 'protobuf >= 3.5.1', ), classifiers=[ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', From 6769578746ee4bbe090b7ae309edbb92fca58f6c Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 13 Jul 2020 11:06:00 -0700 Subject: [PATCH 025/272] feat: add support for proto3 optional fields (#35) Due to popular demand, proto3 recently added the concept of field presence. Fields can be marked as 'optional', which is internally represented via a synthetic oneof. See https://github.com/protocolbuffers/protobuf/releases/tag/v3.12.0 for additional background and information. Generated messages can define optional fields, and message classes gain constants that can be used to check for field presence in instances. These constants are not visible to message instances and therefore don't collide with message field names. Fields can be set as 'optional' using the following syntax Example: class Squid(proto.Message): mantle = proto.Field(proto.INT32, number=1, optional=True) s = Squid() assert not Squid.mantle in s s.mantle = 2112 assert Squid.mantle in s --- packages/proto-plus/proto/fields.py | 6 +- packages/proto-plus/proto/message.py | 25 +++++- packages/proto-plus/setup.py | 2 +- .../proto-plus/tests/test_fields_optional.py | 87 +++++++++++++++++++ 4 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 packages/proto-plus/tests/test_fields_optional.py diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index fb33f7bfde5f..0b00b95966d2 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -24,7 +24,7 @@ class Field: def __init__(self, proto_type, *, number: int, message=None, enum=None, oneof: str = None, - json_name: str = None): + json_name: str = None, optional: bool = False): # This class is not intended to stand entirely alone; # data is augmented by the metaclass for Message. self.mcls_data = {} @@ -47,8 +47,9 @@ def __init__(self, proto_type, *, number: int, self.proto_type = proto_type self.message = message self.enum = enum - self.oneof = oneof self.json_name = json_name + self.optional = optional + self.oneof = oneof # Fields are neither repeated nor maps. # The RepeatedField and MapField subclasses override these values @@ -102,6 +103,7 @@ def descriptor(self): type=proto_type, type_name=type_name, json_name=self.json_name, + proto3_optional=self.optional, ) # Return the descriptor. diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 36eeee1ebd5d..b6c5cd1b6ada 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -129,7 +129,6 @@ def __new__(mcls, name, bases, attrs): # Add the field to the list of fields. fields.append(field) - # If this field is part of a "oneof", ensure the oneof itself # is represented. if field.oneof: @@ -159,6 +158,26 @@ def __new__(mcls, name, bases, attrs): # Increment the field index counter. index += 1 + # As per descriptor.proto, all synthetic oneofs must be ordered after + # 'real' oneofs. + opt_attrs = {} + for field in fields: + if field.optional: + field.oneof = "_{}".format(field.name) + field.descriptor.oneof_index = oneofs[field.oneof] = len(oneofs) + opt_attrs[field.name] = field.name + + # Generating a metaclass dynamically provides class attributes that + # instances can't see. This provides idiomatically named constants + # that enable the following pattern to check for field presence: + # + # class MyMessage(proto.Message): + # field = proto.Field(proto.INT32, number=1, optional=True) + # + # m = MyMessage() + # MyMessage.field in m + mcls = type('AttrsMeta', (mcls,), opt_attrs) + # Determine the filename. # We determine an appropriate proto filename based on the # Python module. @@ -374,8 +393,8 @@ def __init__(self, mapping=None, **kwargs): def __bool__(self): """Return True if any field is truthy, False otherwise.""" - return any([k in self and getattr(self, k) - for k in self._meta.fields.keys()]) + return any(k in self and getattr(self, k) + for k in self._meta.fields.keys()) def __contains__(self, key): """Return True if this field was set to something non-zero on the wire. diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index c816e5e3564a..dd078c32d9c7 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -36,7 +36,7 @@ platforms='Posix; MacOS X', include_package_data=True, install_requires=( - 'protobuf >= 3.5.1', + 'protobuf >= 3.12.0', ), classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/packages/proto-plus/tests/test_fields_optional.py b/packages/proto-plus/tests/test_fields_optional.py new file mode 100644 index 000000000000..311cdfff9064 --- /dev/null +++ b/packages/proto-plus/tests/test_fields_optional.py @@ -0,0 +1,87 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import proto + + +def test_optional_init(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1, optional=True) + + squid_1 = Squid(mass_kg=20) + squid_2 = Squid() + + assert Squid.mass_kg in squid_1 + assert squid_1.mass_kg == 20 + assert not Squid.mass_kg in squid_2 + + squid_2.mass_kg = 30 + assert squid_2.mass_kg == 30 + assert Squid.mass_kg in squid_2 + + del squid_1.mass_kg + assert not Squid.mass_kg in squid_1 + + with pytest.raises(AttributeError): + Squid.shell + + +def test_optional_and_oneof(): + # This test is a defensive check that synthetic oneofs + # don't interfere with user defined oneofs. + + # Oneof defined before an optional + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1, oneof='mass') + mass_lbs = proto.Field(proto.INT32, number=2, oneof='mass') + + iridiphore_num = proto.Field(proto.INT32, number=3, optional=True) + + s = Squid(mass_kg=20) + assert s.mass_kg == 20 + assert not s.mass_lbs + assert not Squid.iridiphore_num in s + + s.iridiphore_num = 600 + assert s.mass_kg == 20 + assert not s.mass_lbs + assert Squid.iridiphore_num in s + + s = Squid(mass_lbs=40, iridiphore_num=600) + assert not s.mass_kg + assert s.mass_lbs == 40 + assert s.iridiphore_num == 600 + + # Oneof defined after an optional + class Clam(proto.Message): + flute_radius = proto.Field(proto.INT32, number=1, optional=True) + + mass_kg = proto.Field(proto.INT32, number=2, oneof='mass') + mass_lbs = proto.Field(proto.INT32, number=3, oneof='mass') + + c = Clam(mass_kg=20) + + assert c.mass_kg == 20 + assert not c.mass_lbs + assert not Clam.flute_radius in c + c.flute_radius = 30 + assert c.mass_kg == 20 + assert not c.mass_lbs + + c = Clam(mass_lbs=40, flute_radius=30) + assert c.mass_lbs == 40 + assert not c.mass_kg + assert c.flute_radius == 30 From 81e2971ed25a9176719d1fa93beb3be0cb4527f0 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 13 Jul 2020 11:46:59 -0700 Subject: [PATCH 026/272] Bump version to 2.0.0 (#36) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index dd078c32d9c7..b85c2109ab49 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name='proto-plus', - version='1.0.0', + version='1.1.0', license='Apache 2.0', author='Dov Shlachter', author_email='dovs@google.com', From cd224ff711bca2c9b8a0ca1d854174fbd367d363 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 15 Jul 2020 19:07:21 -0700 Subject: [PATCH 027/272] feat: add convenience methods to convert to/from json (#39) --- packages/proto-plus/proto/message.py | 26 +++++++++++++ packages/proto-plus/tests/test_json.py | 52 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 packages/proto-plus/tests/test_json.py diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index b6c5cd1b6ada..42800d7a9f74 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -21,6 +21,7 @@ from google.protobuf import descriptor_pb2 from google.protobuf import message from google.protobuf import symbol_database +from google.protobuf.json_format import MessageToJson, Parse from proto import _file_info from proto import _package_info @@ -325,6 +326,31 @@ def deserialize(cls, payload: bytes) -> 'Message': """ return cls(cls.pb().FromString(payload)) + def to_json(cls, instance) -> str: + """Given a message instance, serialize it to json + + Args: + instance: An instance of this message type, or something + compatible (accepted by the type's constructor). + + Returns: + str: The json string representation of the protocol buffer. + """ + return MessageToJson(cls.pb(instance)) + + def from_json(cls, payload) -> 'Message': + """Given a json string representing an instance, + parse it into a message. + + Args: + paylod: A json string representing a message. + + Returns: + ~.Message: An instance of the message class against which this + method was called. + """ + return cls(Parse(payload, cls()._pb)) + class Message(metaclass=MessageMeta): """The abstract base class for a message. diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py new file mode 100644 index 000000000000..f3176ddce19e --- /dev/null +++ b/packages/proto-plus/tests/test_json.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import proto +from google.protobuf.json_format import MessageToJson, Parse + + +def test_message_to_json(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + s = Squid(mass_kg=100) + json = Squid.to_json(s) + json = json.replace(" ", "").replace("\n", "") + assert json == '{"massKg":100}' + + +def test_message_from_json(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + json = '''{ + "massKg": 100 + } + ''' + + s = Squid.from_json(json) + assert s == Squid(mass_kg=100) + + +def test_message_json_round_trip(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + s = Squid(mass_kg=100) + json = Squid.to_json(s) + s2 = Squid.from_json(json) + + assert s == s2 From 5efd5f3440ac8551feb9087d08ee1d20669c3255 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 16 Jul 2020 11:37:03 -0700 Subject: [PATCH 028/272] Blacken proto-pus (#42) --- packages/proto-plus/docs/conf.py | 72 ++++++---- packages/proto-plus/noxfile.py | 50 ++++--- packages/proto-plus/proto/__init__.py | 49 ++++--- packages/proto-plus/proto/_file_info.py | 23 ++-- packages/proto-plus/proto/_package_info.py | 8 +- packages/proto-plus/proto/enums.py | 2 + packages/proto-plus/proto/fields.py | 39 +++--- packages/proto-plus/proto/marshal/__init__.py | 4 +- .../proto/marshal/collections/__init__.py | 6 +- .../proto/marshal/collections/maps.py | 1 + .../proto/marshal/collections/repeated.py | 3 +- packages/proto-plus/proto/marshal/compat.py | 7 +- packages/proto-plus/proto/marshal/marshal.py | 58 ++++---- .../proto-plus/proto/marshal/rules/dates.py | 8 +- .../proto-plus/proto/marshal/rules/enums.py | 9 +- .../proto-plus/proto/marshal/rules/struct.py | 43 +++--- .../proto/marshal/rules/wrappers.py | 1 + packages/proto-plus/proto/message.py | 130 +++++++++--------- packages/proto-plus/proto/modules.py | 19 +-- packages/proto-plus/proto/primitives.py | 1 + packages/proto-plus/proto/utils.py | 10 +- packages/proto-plus/setup.py | 44 +++--- packages/proto-plus/tests/conftest.py | 16 +-- .../proto-plus/tests/test_fields_bytes.py | 44 +++--- .../proto-plus/tests/test_fields_composite.py | 18 +-- .../tests/test_fields_composite_string_ref.py | 34 ++--- packages/proto-plus/tests/test_fields_enum.py | 14 +- packages/proto-plus/tests/test_fields_int.py | 2 +- .../tests/test_fields_map_composite.py | 60 ++++---- .../tests/test_fields_map_scalar.py | 34 ++--- .../proto-plus/tests/test_fields_oneof.py | 20 +-- .../proto-plus/tests/test_fields_optional.py | 8 +- .../tests/test_fields_repeated_composite.py | 16 +-- .../tests/test_fields_repeated_scalar.py | 2 +- .../proto-plus/tests/test_fields_string.py | 38 ++--- packages/proto-plus/tests/test_json.py | 4 +- .../proto-plus/tests/test_marshal_register.py | 3 + .../tests/test_marshal_types_dates.py | 78 +++++------ .../tests/test_marshal_types_enum.py | 6 +- .../tests/test_marshal_types_message.py | 2 +- .../tests/test_marshal_types_struct.py | 98 +++++++------ .../tests/test_marshal_types_wrappers_bool.py | 40 +++--- packages/proto-plus/tests/test_message.py | 54 ++++---- .../proto-plus/tests/test_message_nested.py | 10 +- packages/proto-plus/tests/test_modules.py | 23 ++-- 45 files changed, 612 insertions(+), 599 deletions(-) diff --git a/packages/proto-plus/docs/conf.py b/packages/proto-plus/docs/conf.py index ad92d7f123f2..6a4550853854 100644 --- a/packages/proto-plus/docs/conf.py +++ b/packages/proto-plus/docs/conf.py @@ -14,19 +14,20 @@ # import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- -project = 'Proto Plus for Python' -copyright = '2018, Google LLC' -author = 'Luke Sneeringer' +project = "Proto Plus for Python" +copyright = "2018, Google LLC" +author = "Luke Sneeringer" # The short X.Y version -version = '0.1.0' +version = "0.1.0" # The full version, including alpha/beta/rc tags -release = '0.1.0' +release = "0.1.0" # -- General configuration --------------------------------------------------- @@ -39,24 +40,24 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', - 'sphinx.ext.napoleon', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", + "sphinx.ext.napoleon", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -68,10 +69,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # -- Options for HTML output ------------------------------------------------- @@ -79,7 +80,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -106,7 +107,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'proto-plus-python' +htmlhelp_basename = "proto-plus-python" # -- Options for LaTeX output ------------------------------------------------ @@ -115,15 +116,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignmentAPIClientGeneratorforPython # # 'figure_align': 'htbp', @@ -133,8 +131,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'proto-plus-python.tex', 'Proto Plus for Python Documentation', - 'Luke Sneeringer', 'manual'), + ( + master_doc, + "proto-plus-python.tex", + "Proto Plus for Python Documentation", + "Luke Sneeringer", + "manual", + ), ] @@ -143,8 +146,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'proto-plus-python', 'Proto Plus for Python Documentation', - [author], 1) + ( + master_doc, + "proto-plus-python", + "Proto Plus for Python Documentation", + [author], + 1, + ) ] @@ -154,9 +162,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'proto-plus-python', 'Proto Plus for Python Documentation', - author, 'proto-plus-python', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "proto-plus-python", + "Proto Plus for Python Documentation", + author, + "proto-plus-python", + "One line description of project.", + "Miscellaneous", + ), ] @@ -165,4 +179,4 @@ # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/3/': None} +intersphinx_mapping = {"https://docs.python.org/3/": None} diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index b82cc7cd4221..8e4b6f0a4270 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -18,39 +18,47 @@ import nox -@nox.session(python=['3.5', '3.6', '3.7']) -def unit(session, proto='python'): +@nox.session(python=["3.5", "3.6", "3.7"]) +def unit(session, proto="python"): """Run the unit test suite.""" - session.env['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = proto - session.install('coverage', 'pytest', 'pytest-cov') - session.install('-e', '.') + session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto + session.install("coverage", "pytest", "pytest-cov") + session.install("-e", ".") session.run( - 'py.test', - '-W=error', - '--quiet', - '--cov=proto', - '--cov-config=.coveragerc', - '--cov-report=term', - '--cov-report=html', - os.path.join('tests', ''), + "py.test", + "-W=error", + "--quiet", + "--cov=proto", + "--cov-config=.coveragerc", + "--cov-report=term", + "--cov-report=html", + os.path.join("tests", ""), ) -@nox.session(python=['3.5', '3.6', '3.7']) +@nox.session(python=["3.5", "3.6", "3.7"]) def unitcpp(session): - return unit(session, proto='cpp') + return unit(session, proto="cpp") -@nox.session(python='3.6') +@nox.session(python="3.6") def docs(session): """Build the docs.""" - session.install('sphinx', 'sphinx_rtd_theme') - session.install('.') + session.install("sphinx", "sphinx_rtd_theme") + session.install(".") # Build the docs! - session.run('rm', '-rf', 'docs/_build/') - session.run('sphinx-build', '-W', '-b', 'html', '-d', - 'docs/_build/doctrees', 'docs/', 'docs/_build/html/') + session.run("rm", "-rf", "docs/_build/") + session.run( + "sphinx-build", + "-W", + "-b", + "html", + "-d", + "docs/_build/doctrees", + "docs/", + "docs/_build/html/", + ) diff --git a/packages/proto-plus/proto/__init__.py b/packages/proto-plus/proto/__init__.py index 5cf75cdd0c2d..80c7948b5ced 100644 --- a/packages/proto-plus/proto/__init__.py +++ b/packages/proto-plus/proto/__init__.py @@ -42,30 +42,29 @@ __all__ = ( - 'Enum', - 'Field', - 'MapField', - 'RepeatedField', - 'Marshal', - 'Message', - 'module', - + "Enum", + "Field", + "MapField", + "RepeatedField", + "Marshal", + "Message", + "module", # Expose the types directly. - 'DOUBLE', - 'FLOAT', - 'INT64', - 'UINT64', - 'INT32', - 'FIXED64', - 'FIXED32', - 'BOOL', - 'STRING', - 'MESSAGE', - 'BYTES', - 'UINT32', - 'ENUM', - 'SFIXED32', - 'SFIXED64', - 'SINT32', - 'SINT64', + "DOUBLE", + "FLOAT", + "INT64", + "UINT64", + "INT32", + "FIXED64", + "FIXED32", + "BOOL", + "STRING", + "MESSAGE", + "BYTES", + "UINT32", + "ENUM", + "SFIXED32", + "SFIXED64", + "SINT32", + "SINT64", ) diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index b50526c0b597..9fd5fc7d505e 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -24,8 +24,11 @@ from proto.marshal.rules.message import MessageRule -class _FileInfo(collections.namedtuple( - '_FileInfo', ['descriptor', 'messages', 'enums', 'name', 'nested'])): +class _FileInfo( + collections.namedtuple( + "_FileInfo", ["descriptor", "messages", "enums", "name", "nested"] + ) +): registry = {} # Mapping[str, '_FileInfo'] def generate_file_pb(self): @@ -44,9 +47,8 @@ def generate_file_pb(self): # Salt the filename in the descriptor. # This allows re-use of the filename by other proto messages if # needed (e.g. if __all__ is not used). - self.descriptor.name = '{prefix}_{salt}.proto'.format( - prefix=self.descriptor.name[:-6], - salt=str(uuid.uuid4())[0:8], + self.descriptor.name = "{prefix}_{salt}.proto".format( + prefix=self.descriptor.name[:-6], salt=str(uuid.uuid4())[0:8], ) # Add the file descriptor. @@ -62,7 +64,7 @@ def generate_file_pb(self): pb_message = reflection.GeneratedProtocolMessageType( descriptor.name, (message.Message,), - {'DESCRIPTOR': descriptor, '__module__': None}, + {"DESCRIPTOR": descriptor, "__module__": None}, ) # Register the message with the marshal so it is wrapped @@ -73,8 +75,7 @@ def generate_file_pb(self): # message subclass, which is what we need to use. proto_plus_message._meta._pb = pb_message proto_plus_message._meta.marshal.register( - pb_message, - MessageRule(pb_message, proto_plus_message) + pb_message, MessageRule(pb_message, proto_plus_message) ) # Iterate over any fields on the message and, if their type @@ -122,10 +123,8 @@ def ready(self, new_class): # manifest has been populated. module = inspect.getmodule(new_class) manifest = frozenset() - if hasattr(module, '__protobuf__'): - manifest = module.__protobuf__.manifest.difference( - {new_class.__name__}, - ) + if hasattr(module, "__protobuf__"): + manifest = module.__protobuf__.manifest.difference({new_class.__name__},) if not all([hasattr(module, i) for i in manifest]): return False diff --git a/packages/proto-plus/proto/_package_info.py b/packages/proto-plus/proto/_package_info.py index b0265dc2cb0e..d01dd79bc780 100644 --- a/packages/proto-plus/proto/_package_info.py +++ b/packages/proto-plus/proto/_package_info.py @@ -32,12 +32,12 @@ def compile(name, attrs): """ # Pull a reference to the module where this class is being # declared. - module = sys.modules.get(attrs.get('__module__')) - proto_module = getattr(module, '__protobuf__', object()) + module = sys.modules.get(attrs.get("__module__")) + proto_module = getattr(module, "__protobuf__", object()) # A package should be present; get the marshal from there. - package = getattr(proto_module, 'package', '') - marshal = Marshal(name=getattr(proto_module, 'marshal', package)) + package = getattr(proto_module, "package", "") + marshal = Marshal(name=getattr(proto_module, "marshal", package)) # Done; return the data. return (package, marshal) diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index b02843186faa..ef1af5912a1d 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -20,6 +20,7 @@ class ProtoEnumMeta(enum.EnumMeta): """A metaclass for building and registering protobuf enums.""" + def __new__(mcls, name, bases, attrs): # Do not do any special behavior for `proto.Enum` itself. if bases[0] == enum.IntEnum: @@ -41,4 +42,5 @@ def __new__(mcls, name, bases, attrs): class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): """A enum object that also builds a protobuf enum descriptor.""" + pass diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index 0b00b95966d2..c8724673af13 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -22,9 +22,17 @@ class Field: """A representation of a type of field in protocol buffers.""" - def __init__(self, proto_type, *, number: int, - message=None, enum=None, oneof: str = None, - json_name: str = None, optional: bool = False): + def __init__( + self, + proto_type, + *, + number: int, + message=None, + enum=None, + oneof: str = None, + json_name: str = None, + optional: bool = False + ): # This class is not intended to stand entirely alone; # data is augmented by the metaclass for Message. self.mcls_data = {} @@ -70,13 +78,12 @@ def descriptor(self): type_name = None if isinstance(self.message, str): if not self.message.startswith(self.package): - self.message = '{package}.{name}'.format( - package=self.package, - name=self.message, + self.message = "{package}.{name}".format( + package=self.package, name=self.message, ) type_name = self.message elif self.message: - if hasattr(self.message, 'DESCRIPTOR'): + if hasattr(self.message, "DESCRIPTOR"): type_name = self.message.DESCRIPTOR.full_name else: type_name = self.message.meta.full_name @@ -112,12 +119,12 @@ def descriptor(self): @property def name(self) -> str: """Return the name of the field.""" - return self.mcls_data['name'] + return self.mcls_data["name"] @property def package(self) -> str: """Return the package of the field.""" - return self.mcls_data['package'] + return self.mcls_data["package"] @property def pb_type(self): @@ -131,7 +138,7 @@ def pb_type(self): return None # Return the internal protobuf message. - if hasattr(self.message, '_meta'): + if hasattr(self.message, "_meta"): return self.message.pb() return self.message @@ -139,8 +146,7 @@ def pb_type(self): class RepeatedField(Field): """A representation of a repeated field in protocol buffers.""" - def __init__(self, proto_type, *, number: int, - message=None, enum=None): + def __init__(self, proto_type, *, number: int, message=None, enum=None): super().__init__(proto_type, number=number, message=message, enum=enum) self.repeated = True @@ -148,14 +154,13 @@ def __init__(self, proto_type, *, number: int, class MapField(Field): """A representation of a map field in protocol buffers.""" - def __init__(self, key_type, value_type, *, number: int, - message=None, enum=None): + def __init__(self, key_type, value_type, *, number: int, message=None, enum=None): super().__init__(value_type, number=number, message=message, enum=enum) self.map_key_type = key_type __all__ = ( - 'Field', - 'MapField', - 'RepeatedField', + "Field", + "MapField", + "RepeatedField", ) diff --git a/packages/proto-plus/proto/marshal/__init__.py b/packages/proto-plus/proto/marshal/__init__.py index 71f1c7218e05..621ea3695f93 100644 --- a/packages/proto-plus/proto/marshal/__init__.py +++ b/packages/proto-plus/proto/marshal/__init__.py @@ -15,6 +15,4 @@ from .marshal import Marshal -__all__ = ( - 'Marshal', -) +__all__ = ("Marshal",) diff --git a/packages/proto-plus/proto/marshal/collections/__init__.py b/packages/proto-plus/proto/marshal/collections/__init__.py index 78f4bf2f918f..4b80a546c26a 100644 --- a/packages/proto-plus/proto/marshal/collections/__init__.py +++ b/packages/proto-plus/proto/marshal/collections/__init__.py @@ -18,7 +18,7 @@ __all__ = ( - 'MapComposite', - 'Repeated', - 'RepeatedComposite', + "MapComposite", + "Repeated", + "RepeatedComposite", ) diff --git a/packages/proto-plus/proto/marshal/collections/maps.py b/packages/proto-plus/proto/marshal/collections/maps.py index 5fced6f0a974..8ed11349d22f 100644 --- a/packages/proto-plus/proto/marshal/collections/maps.py +++ b/packages/proto-plus/proto/marshal/collections/maps.py @@ -23,6 +23,7 @@ class MapComposite(collections.abc.MutableMapping): This implements the full Python MutableMapping interface, but all methods modify the underlying field container directly. """ + @cached_property def _pb_type(self): """Return the protocol buffer type for this sequence.""" diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index 796d39d62964..3063e546b5e7 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -45,7 +45,7 @@ def __delitem__(self, key): del self.pb[key] def __eq__(self, other): - if hasattr(other, 'pb'): + if hasattr(other, "pb"): return tuple(self.pb) == tuple(other.pb) return tuple(self.pb) == tuple(other) @@ -85,6 +85,7 @@ class RepeatedComposite(Repeated): This implements the full Python MutableSequence interface, but all methods modify the underlying field container directly. """ + @cached_property def _pb_type(self): """Return the protocol buffer type for this sequence.""" diff --git a/packages/proto-plus/proto/marshal/compat.py b/packages/proto-plus/proto/marshal/compat.py index 28813c34fd6c..c1960d5752eb 100644 --- a/packages/proto-plus/proto/marshal/compat.py +++ b/packages/proto-plus/proto/marshal/compat.py @@ -19,6 +19,7 @@ # not be included. from google.protobuf.internal import containers + try: from google.protobuf.pyext import _message except ImportError: @@ -34,7 +35,7 @@ __all__ = ( - 'repeated_composite_types', - 'repeated_scalar_types', - 'map_composite_types', + "repeated_composite_types", + "repeated_scalar_types", + "map_composite_types", ) diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index e0983ee0376d..11c3cf574ba4 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -35,7 +35,7 @@ class Rule(abc.ABC): @classmethod def __subclasshook__(cls, C): - if hasattr(C, 'to_python') and hasattr(C, 'to_proto'): + if hasattr(C, "to_python") and hasattr(C, "to_proto"): return True return NotImplemented @@ -58,6 +58,7 @@ class BaseMarshal: The primary implementation of this is :class:`Marshal`, which should usually be used instead of this class directly. """ + def __init__(self): self._rules = {} self._noop = NoopRule() @@ -87,15 +88,19 @@ class TimestampRule: # Sanity check: Do not register anything to a class that is not # a protocol buffer message. if not issubclass(proto_type, (message.Message, enum.IntEnum)): - raise TypeError('Only enums and protocol buffer messages may be ' - 'registered to the marshal.') + raise TypeError( + "Only enums and protocol buffer messages may be " + "registered to the marshal." + ) # If a rule was provided, register it and be done. if rule: # Ensure the rule implements Rule. if not isinstance(rule, Rule): - raise TypeError('Marshal rule instances must implement ' - '`to_proto` and `to_python` methods.') + raise TypeError( + "Marshal rule instances must implement " + "`to_proto` and `to_python` methods." + ) # Register the rule. self._rules[proto_type] = rule @@ -106,12 +111,15 @@ class TimestampRule: def register_rule_class(rule_class: type): # Ensure the rule class is a valid rule. if not issubclass(rule_class, Rule): - raise TypeError('Marshal rule subclasses must implement ' - '`to_proto` and `to_python` methods.') + raise TypeError( + "Marshal rule subclasses must implement " + "`to_proto` and `to_python` methods." + ) # Register the rule class. self._rules[proto_type] = rule_class() return rule_class + return register_rule_class def reset(self): @@ -162,8 +170,11 @@ def to_proto(self, proto_type, value, *, strict: bool = False): # The protos in google/protobuf/struct.proto are exceptional cases, # because they can and should represent themselves as lists and dicts. # These cases are handled in their rule classes. - if proto_type not in (struct_pb2.Value, - struct_pb2.ListValue, struct_pb2.Struct): + if proto_type not in ( + struct_pb2.Value, + struct_pb2.ListValue, + struct_pb2.Struct, + ): # For our repeated and map view objects, simply return the # underlying pb. if isinstance(value, (Repeated, MapComposite)): @@ -171,9 +182,7 @@ def to_proto(self, proto_type, value, *, strict: bool = False): # Convert lists and tuples recursively. if isinstance(value, (list, tuple)): - return type(value)( - [self.to_proto(proto_type, i) for i in value], - ) + return type(value)([self.to_proto(proto_type, i) for i in value],) # Convert dictionaries recursively when the proto type is a map. # This is slightly more complicated than converting a list or tuple @@ -183,10 +192,13 @@ def to_proto(self, proto_type, value, *, strict: bool = False): # a FoosEntry with a `key` field, `value` field, and a `map_entry` # annotation. We need to do the conversion based on the `value` # field's type. - if isinstance(value, dict) and (proto_type.DESCRIPTOR.has_options and - proto_type.DESCRIPTOR.GetOptions().map_entry): - return {k: self.to_proto(type(proto_type().value), v) - for k, v in value.items()} + if isinstance(value, dict) and ( + proto_type.DESCRIPTOR.has_options + and proto_type.DESCRIPTOR.GetOptions().map_entry + ): + return { + k: self.to_proto(type(proto_type().value), v) for k, v in value.items() + } # Convert ordinary values. rule = self._rules.get(proto_type, self._noop) @@ -195,10 +207,9 @@ def to_proto(self, proto_type, value, *, strict: bool = False): # Sanity check: If we are in strict mode, did we get the value we want? if strict and not isinstance(pb_value, proto_type): raise TypeError( - 'Parameter must be instance of the same class; ' - 'expected {expected}, got {got}'.format( - expected=proto_type.__name__, - got=pb_value.__class__.__name__, + "Parameter must be instance of the same class; " + "expected {expected}, got {got}".format( + expected=proto_type.__name__, got=pb_value.__class__.__name__, ), ) @@ -213,6 +224,7 @@ class Marshal(BaseMarshal): adds identity tracking: multiple instantiations of :class:`Marshal` with the same name will provide the same instance. """ + _instances = {} def __new__(cls, *, name: str): @@ -236,7 +248,7 @@ def __init__(self, *, name: str): same marshal each time. """ self._name = name - if not hasattr(self, '_rules'): + if not hasattr(self, "_rules"): super().__init__() @@ -250,6 +262,4 @@ def to_proto(self, value): return value -__all__ = ( - 'Marshal', -) +__all__ = ("Marshal",) diff --git a/packages/proto-plus/proto/marshal/rules/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py index b365fdf2165c..37a2844d6b65 100644 --- a/packages/proto-plus/proto/marshal/rules/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -28,21 +28,20 @@ class TimestampRule: precision matters, it is recommended to interact with the internal proto directly. """ + def to_python(self, value, *, absent: bool = None) -> datetime: if isinstance(value, timestamp_pb2.Timestamp): if absent: return None return datetime.fromtimestamp( - value.seconds + value.nanos / 1e9, - tz=timezone.utc, + value.seconds + value.nanos / 1e9, tz=timezone.utc, ) return value def to_proto(self, value) -> timestamp_pb2.Timestamp: if isinstance(value, datetime): return timestamp_pb2.Timestamp( - seconds=int(value.timestamp()), - nanos=value.microsecond * 1000, + seconds=int(value.timestamp()), nanos=value.microsecond * 1000, ) return value @@ -55,6 +54,7 @@ class DurationRule: precision matters, it is recommended to interact with the internal proto directly. """ + def to_python(self, value, *, absent: bool = None) -> timedelta: if isinstance(value, duration_pb2.Duration): return timedelta( diff --git a/packages/proto-plus/proto/marshal/rules/enums.py b/packages/proto-plus/proto/marshal/rules/enums.py index 7ef2f160d82a..d965666c59c0 100644 --- a/packages/proto-plus/proto/marshal/rules/enums.py +++ b/packages/proto-plus/proto/marshal/rules/enums.py @@ -34,10 +34,11 @@ def to_python(self, value, *, absent: bool = None): # # However, it is useful to make some noise about it so # the user realizes that an unexpected value came along. - warnings.warn('Unrecognized {name} enum value: {value}'.format( - name=self._enum.__name__, - value=value, - )) + warnings.warn( + "Unrecognized {name} enum value: {value}".format( + name=self._enum.__name__, value=value, + ) + ) return value def to_proto(self, value): diff --git a/packages/proto-plus/proto/marshal/rules/struct.py b/packages/proto-plus/proto/marshal/rules/struct.py index e6a6f714b978..dc183407c026 100644 --- a/packages/proto-plus/proto/marshal/rules/struct.py +++ b/packages/proto-plus/proto/marshal/rules/struct.py @@ -32,26 +32,22 @@ def to_python(self, value, *, absent: bool = None): Note that setting ``null_value`` is distinct from not setting a value, and the absent value will raise an exception. """ - kind = value.WhichOneof('kind') - if kind == 'null_value': + kind = value.WhichOneof("kind") + if kind == "null_value": return None - if kind == 'bool_value': + if kind == "bool_value": return bool(value.bool_value) - if kind == 'number_value': + if kind == "number_value": return float(value.number_value) - if kind == 'string_value': + if kind == "string_value": return str(value.string_value) - if kind == 'struct_value': + if kind == "struct_value": return self._marshal.to_python( - struct_pb2.Struct, - value.struct_value, - absent=False, + struct_pb2.Struct, value.struct_value, absent=False, ) - if kind == 'list_value': + if kind == "list_value": return self._marshal.to_python( - struct_pb2.ListValue, - value.list_value, - absent=False, + struct_pb2.ListValue, value.list_value, absent=False, ) raise AttributeError @@ -75,7 +71,7 @@ def to_proto(self, value) -> struct_pb2.Value: return struct_pb2.Value( struct_value=self._marshal.to_proto(struct_pb2.Struct, value), ) - raise ValueError('Unable to coerce value: %r' % value) + raise ValueError("Unable to coerce value: %r" % value) class ListValueRule: @@ -97,9 +93,9 @@ def to_proto(self, value) -> struct_pb2.ListValue: return struct_pb2.ListValue(values=[v for v in value.pb]) # We got a list (or something list-like); convert it. - return struct_pb2.ListValue(values=[ - self._marshal.to_proto(struct_pb2.Value, v) for v in value - ]) + return struct_pb2.ListValue( + values=[self._marshal.to_proto(struct_pb2.Value, v) for v in value] + ) class StructRule: @@ -118,13 +114,12 @@ def to_proto(self, value) -> struct_pb2.Struct: if isinstance(value, struct_pb2.Struct): return value if isinstance(value, maps.MapComposite): - return struct_pb2.Struct( - fields={k: v for k, v in value.pb.items()}, - ) + return struct_pb2.Struct(fields={k: v for k, v in value.pb.items()},) # We got a dict (or something dict-like); convert it. - answer = struct_pb2.Struct(fields={ - k: self._marshal.to_proto(struct_pb2.Value, v) - for k, v in value.items() - }) + answer = struct_pb2.Struct( + fields={ + k: self._marshal.to_proto(struct_pb2.Value, v) for k, v in value.items() + } + ) return answer diff --git a/packages/proto-plus/proto/marshal/rules/wrappers.py b/packages/proto-plus/proto/marshal/rules/wrappers.py index bfd4b78efc43..5bc89e595851 100644 --- a/packages/proto-plus/proto/marshal/rules/wrappers.py +++ b/packages/proto-plus/proto/marshal/rules/wrappers.py @@ -25,6 +25,7 @@ class WrapperRule: These are effectively similar to the protobuf primitives except that None becomes a possible value. """ + def to_python(self, value, *, absent: bool = None): if isinstance(value, self._proto_type): if absent: diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 42800d7a9f74..b4ba1d388346 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -45,16 +45,16 @@ def __new__(mcls, name, bases, attrs): package, marshal = _package_info.compile(name, attrs) # Determine the local path of this proto component within the file. - local_path = tuple(attrs.get('__qualname__', name).split('.')) + local_path = tuple(attrs.get("__qualname__", name).split(".")) # Sanity check: We get the wrong full name if a class is declared # inside a function local scope; correct this. - if '' in local_path: - ix = local_path.index('') - local_path = local_path[:ix - 1] + local_path[ix + 1:] + if "" in local_path: + ix = local_path.index("") + local_path = local_path[: ix - 1] + local_path[ix + 1 :] # Determine the full name in protocol buffers. - full_name = '.'.join((package,) + local_path).lstrip('.') + full_name = ".".join((package,) + local_path).lstrip(".") # Special case: Maps. Map fields are special; they are essentially # shorthand for a nested message and a repeated field of that message. @@ -65,11 +65,9 @@ def __new__(mcls, name, bases, attrs): continue # Determine the name of the entry message. - msg_name = '{pascal_key}Entry'.format( + msg_name = "{pascal_key}Entry".format( pascal_key=re.sub( - r'_\w', - lambda m: m.group()[1:].upper(), - key, + r"_\w", lambda m: m.group()[1:].upper(), key, ).replace(key[0], key[0].upper(), 1), ) @@ -81,25 +79,24 @@ def __new__(mcls, name, bases, attrs): # This is only an issue in Python 3.5, where the order is # random (and the wrong order causes the pool to refuse to add # the descriptor because reasons). - entry_attrs = collections.OrderedDict({ - '__module__': attrs.get('__module__', None), - '__qualname__': '{prefix}.{name}'.format( - prefix=attrs.get('__qualname__', name), - name=msg_name, - ), - '_pb_options': {'map_entry': True}, - }) - entry_attrs['key'] = Field(field.map_key_type, number=1) - entry_attrs['value'] = Field(field.proto_type, number=2, - enum=field.enum, - message=field.message, + entry_attrs = collections.OrderedDict( + { + "__module__": attrs.get("__module__", None), + "__qualname__": "{prefix}.{name}".format( + prefix=attrs.get("__qualname__", name), name=msg_name, + ), + "_pb_options": {"map_entry": True}, + } + ) + entry_attrs["key"] = Field(field.map_key_type, number=1) + entry_attrs["value"] = Field( + field.proto_type, number=2, enum=field.enum, message=field.message, ) attrs[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) # Create the repeated field for the entry message. - attrs[key] = RepeatedField(ProtoType.MESSAGE, - number=field.number, - message=attrs[msg_name], + attrs[key] = RepeatedField( + ProtoType.MESSAGE, number=field.number, message=attrs[msg_name], ) # Okay, now we deal with all the rest of the fields. @@ -122,10 +119,10 @@ def __new__(mcls, name, bases, attrs): # constructor because we can derive it from the metaclass. # (The goal is to make the declaration syntax as nice as possible.) field.mcls_data = { - 'name': key, - 'parent_name': full_name, - 'index': index, - 'package': package, + "name": key, + "parent_name": full_name, + "index": index, + "package": package, } # Add the field to the list of fields. @@ -143,7 +140,7 @@ def __new__(mcls, name, bases, attrs): # construct our file descriptor proto). if field.message and not isinstance(field.message, str): field_msg = field.message - if hasattr(field_msg, 'pb') and callable(field_msg.pb): + if hasattr(field_msg, "pb") and callable(field_msg.pb): field_msg = field_msg.pb() # Sanity check: The field's message may not yet be defined if @@ -177,13 +174,13 @@ def __new__(mcls, name, bases, attrs): # # m = MyMessage() # MyMessage.field in m - mcls = type('AttrsMeta', (mcls,), opt_attrs) + mcls = type("AttrsMeta", (mcls,), opt_attrs) # Determine the filename. # We determine an appropriate proto filename based on the # Python module. - filename = '{0}.proto'.format( - attrs.get('__module__', name.lower()).replace('.', '/') + filename = "{0}.proto".format( + attrs.get("__module__", name.lower()).replace(".", "/") ) # Get or create the information about the file, including the @@ -192,9 +189,7 @@ def __new__(mcls, name, bases, attrs): filename, _file_info._FileInfo( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, - package=package, - syntax='proto3', + name=filename, package=package, syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), @@ -210,14 +205,15 @@ def __new__(mcls, name, bases, attrs): file_info.descriptor.dependency.append(proto_import) # Retrieve any message options. - opts = descriptor_pb2.MessageOptions(**attrs.pop('_pb_options', {})) + opts = descriptor_pb2.MessageOptions(**attrs.pop("_pb_options", {})) # Create the underlying proto descriptor. desc = descriptor_pb2.DescriptorProto( name=name, field=[i.descriptor for i in fields], - oneof_decl=[descriptor_pb2.OneofDescriptorProto(name=i) - for i in oneofs.keys()], + oneof_decl=[ + descriptor_pb2.OneofDescriptorProto(name=i) for i in oneofs.keys() + ], options=opts, ) @@ -225,9 +221,7 @@ def __new__(mcls, name, bases, attrs): # attached as nested types here. for child_path in copy.copy(file_info.nested).keys(): if local_path == child_path[:-1]: - desc.nested_type.add().MergeFrom( - file_info.nested.pop(child_path), - ) + desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path),) # Add the descriptor to the file if it is a top-level descriptor, # or to a "holding area" for nested messages otherwise. @@ -237,7 +231,7 @@ def __new__(mcls, name, bases, attrs): file_info.nested[local_path] = desc # Create the MessageInfo instance to be attached to this message. - attrs['_meta'] = _MessageInfo( + attrs["_meta"] = _MessageInfo( fields=fields, full_name=full_name, marshal=marshal, @@ -288,9 +282,7 @@ def pb(cls, obj=None, *, coerce: bool = False): if coerce: obj = cls(obj) else: - raise TypeError('%r is not an instance of %s' % ( - obj, cls.__name__, - )) + raise TypeError("%r is not an instance of %s" % (obj, cls.__name__,)) return obj._pb def wrap(cls, pb): @@ -314,7 +306,7 @@ def serialize(cls, instance) -> bytes: """ return cls.pb(instance, coerce=True).SerializeToString() - def deserialize(cls, payload: bytes) -> 'Message': + def deserialize(cls, payload: bytes) -> "Message": """Given a serialized proto, deserialize it into a Message instance. Args: @@ -338,7 +330,7 @@ def to_json(cls, instance) -> str: """ return MessageToJson(cls.pb(instance)) - def from_json(cls, payload) -> 'Message': + def from_json(cls, payload) -> "Message": """Given a json string representing an instance, parse it into a message. @@ -361,6 +353,7 @@ class Message(metaclass=MessageMeta): kwargs (dict): Keys and values corresponding to the fields of the message. """ + def __init__(self, mapping=None, **kwargs): # We accept several things for `mapping`: # * An instance of this class. @@ -370,10 +363,12 @@ def __init__(self, mapping=None, **kwargs): # # Sanity check: Did we get something not on that list? Error if so. if mapping and not isinstance( - mapping, (collections.abc.Mapping, type(self), self._meta.pb)): - raise TypeError('Invalid constructor input for %s: %r' % ( - self.__class__.__name__, mapping, - )) + mapping, (collections.abc.Mapping, type(self), self._meta.pb) + ): + raise TypeError( + "Invalid constructor input for %s: %r" + % (self.__class__.__name__, mapping,) + ) # Handle the first two cases: they both involve keeping # a copy of the underlying protobuf descriptor instance. @@ -389,7 +384,7 @@ def __init__(self, mapping=None, **kwargs): # this behavior, because `MessageRule` actually does want to # wrap the original argument it was given. The `wrap` method # on the metaclass is the public API for this behavior. - if not kwargs.pop('__wrap_original', False): + if not kwargs.pop("__wrap_original", False): mapping = copy.copy(mapping) self._pb = mapping if kwargs: @@ -419,8 +414,7 @@ def __init__(self, mapping=None, **kwargs): def __bool__(self): """Return True if any field is truthy, False otherwise.""" - return any(k in self and getattr(self, k) - for k in self._meta.fields.keys()) + return any(k in self and getattr(self, k) for k in self._meta.fields.keys()) def __contains__(self, key): """Return True if this field was set to something non-zero on the wire. @@ -522,7 +516,7 @@ def __setattr__(self, key, value): For well-known protocol buffer types which are marshalled, either the protocol buffer object or the Python equivalent is accepted. """ - if key.startswith('_'): + if key.startswith("_"): return super().__setattr__(key, value) marshal = self._meta.marshal pb_type = self._meta.fields[key].pb_type @@ -552,19 +546,21 @@ class _MessageInfo: options (~.descriptor_pb2.MessageOptions): Any options that were set on the message. """ - def __init__(self, *, - fields: List[Field], package: str, full_name: str, - marshal: Marshal, options: descriptor_pb2.MessageOptions - ) -> None: + + def __init__( + self, + *, + fields: List[Field], + package: str, + full_name: str, + marshal: Marshal, + options: descriptor_pb2.MessageOptions + ) -> None: self.package = package self.full_name = full_name self.options = options - self.fields = collections.OrderedDict([ - (i.name, i) for i in fields - ]) - self.fields_by_number = collections.OrderedDict([ - (i.number, i) for i in fields - ]) + self.fields = collections.OrderedDict([(i.name, i) for i in fields]) + self.fields_by_number = collections.OrderedDict([(i.number, i) for i in fields]) self.marshal = marshal self._pb = None @@ -578,6 +574,4 @@ def pb(self) -> Type[message.Message]: return self._pb -__all__ = ( - 'Message', -) +__all__ = ("Message",) diff --git a/packages/proto-plus/proto/modules.py b/packages/proto-plus/proto/modules.py index a5ba94ffd42e..224213f40d6c 100644 --- a/packages/proto-plus/proto/modules.py +++ b/packages/proto-plus/proto/modules.py @@ -16,13 +16,14 @@ import collections -_ProtoModule = collections.namedtuple('ProtoModule', - ['package', 'marshal', 'manifest'], +_ProtoModule = collections.namedtuple( + "ProtoModule", ["package", "marshal", "manifest"], ) -def define_module(*, package: str, marshal: str = None, - manifest: Set[str] = frozenset()) -> _ProtoModule: +def define_module( + *, package: str, marshal: str = None, manifest: Set[str] = frozenset() +) -> _ProtoModule: """Define a protocol buffers module. The settings defined here are used for all protobuf messages @@ -38,13 +39,7 @@ def define_module(*, package: str, marshal: str = None, """ if not marshal: marshal = package - return _ProtoModule( - package=package, - marshal=marshal, - manifest=frozenset(manifest), - ) + return _ProtoModule(package=package, marshal=marshal, manifest=frozenset(manifest),) -__all__ = ( - 'define_module', -) +__all__ = ("define_module",) diff --git a/packages/proto-plus/proto/primitives.py b/packages/proto-plus/proto/primitives.py index 683aaa20d280..cff2094c678b 100644 --- a/packages/proto-plus/proto/primitives.py +++ b/packages/proto-plus/proto/primitives.py @@ -17,6 +17,7 @@ class ProtoType(enum.IntEnum): """The set of basic types in protocol buffers.""" + # These values come from google/protobuf/descriptor.proto DOUBLE = 1 FLOAT = 2 diff --git a/packages/proto-plus/proto/utils.py b/packages/proto-plus/proto/utils.py index 90172947bd42..6367598027d2 100644 --- a/packages/proto-plus/proto/utils.py +++ b/packages/proto-plus/proto/utils.py @@ -27,11 +27,12 @@ def cached_property(fx): Returns: Callable[]: The wrapped function. """ + @functools.wraps(fx) def inner(self): # Sanity check: If there is no cache at all, create an empty cache. - if not hasattr(self, '_cached_values'): - object.__setattr__(self, '_cached_values', {}) + if not hasattr(self, "_cached_values"): + object.__setattr__(self, "_cached_values", {}) # If and only if the function's result is not in the cache, # run the function. @@ -40,9 +41,8 @@ def inner(self): # Return the value from cache. return self._cached_values[fx.__name__] + return property(inner) -__all__ = ( - 'cached_property', -) +__all__ = ("cached_property",) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index b85c2109ab49..0618351aa6f2 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -20,35 +20,33 @@ PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) -with io.open(os.path.join(PACKAGE_ROOT, 'README.rst')) as file_obj: +with io.open(os.path.join(PACKAGE_ROOT, "README.rst")) as file_obj: README = file_obj.read() setup( - name='proto-plus', - version='1.1.0', - license='Apache 2.0', - author='Dov Shlachter', - author_email='dovs@google.com', - url='https://github.com/googleapis/proto-plus-python.git', - packages=find_packages(exclude=['docs', 'tests']), - description='Beautiful, Pythonic protocol buffers.', + name="proto-plus", + version="1.1.0", + license="Apache 2.0", + author="Dov Shlachter", + author_email="dovs@google.com", + url="https://github.com/googleapis/proto-plus-python.git", + packages=find_packages(exclude=["docs", "tests"]), + description="Beautiful, Pythonic protocol buffers.", long_description=README, - platforms='Posix; MacOS X', + platforms="Posix; MacOS X", include_package_data=True, - install_requires=( - 'protobuf >= 3.12.0', - ), + install_requires=("protobuf >= 3.12.0",), classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Software Development :: Code Generators', - 'Topic :: Software Development :: Libraries :: Python Modules', + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Software Development :: Code Generators", + "Topic :: Software Development :: Libraries :: Python Modules", ], zip_safe=False, ) diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index a2d577408f9f..2cf373527c00 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -33,8 +33,8 @@ def pytest_runtest_setup(item): pool = descriptor_pool.DescriptorPool() sym_db = symbol_database.SymbolDatabase(pool=pool) item._mocks = ( - mock.patch.object(descriptor_pool, 'Default', return_value=pool), - mock.patch.object(symbol_database, 'Default', return_value=sym_db), + mock.patch.object(descriptor_pool, "Default", return_value=pool), + mock.patch.object(symbol_database, "Default", return_value=sym_db), ) [i.start() for i in item._mocks] @@ -53,7 +53,7 @@ def pytest_runtest_setup(item): # onto the pb2 modules. reloaded = set() for name in dir(item.module): - if name.endswith('_pb2') and not name.startswith('test_'): + if name.endswith("_pb2") and not name.startswith("test_"): module = getattr(item.module, name) pool.AddSerializedFile(module.DESCRIPTOR.serialized_pb) fd = pool.FindFileByName(module.DESCRIPTOR.name) @@ -68,11 +68,11 @@ def pytest_runtest_setup(item): # If the marshal had previously registered the old message classes, # then reload the appropriate modules so the marshal is using the new ones. - if 'wrappers_pb2' in reloaded: + if "wrappers_pb2" in reloaded: imp.reload(rules.wrappers) - if 'struct_pb2' in reloaded: + if "struct_pb2" in reloaded: imp.reload(rules.struct) - if reloaded.intersection({'timestamp_pb2', 'duration_pb2'}): + if reloaded.intersection({"timestamp_pb2", "duration_pb2"}): imp.reload(rules.dates) @@ -85,9 +85,7 @@ def _register_messages(scope, iterable, sym_db): """Create and register messages from the file descriptor.""" for name, descriptor in iterable.items(): new_msg = reflection.GeneratedProtocolMessageType( - name, - (message.Message,), - {'DESCRIPTOR': descriptor, '__module__': None}, + name, (message.Message,), {"DESCRIPTOR": descriptor, "__module__": None}, ) sym_db.RegisterMessage(new_msg) setattr(scope, name, new_msg) diff --git a/packages/proto-plus/tests/test_fields_bytes.py b/packages/proto-plus/tests/test_fields_bytes.py index eba15daed8dd..3e595d7a3897 100644 --- a/packages/proto-plus/tests/test_fields_bytes.py +++ b/packages/proto-plus/tests/test_fields_bytes.py @@ -22,12 +22,12 @@ class Foo(proto.Message): bar = proto.Field(proto.BYTES, number=1) baz = proto.Field(proto.BYTES, number=2) - foo = Foo(bar=b'spam') - assert foo.bar == b'spam' - assert foo.baz == b'' + foo = Foo(bar=b"spam") + assert foo.bar == b"spam" + assert foo.baz == b"" assert not foo.baz - assert Foo.pb(foo).bar == b'spam' - assert Foo.pb(foo).baz == b'' + assert Foo.pb(foo).bar == b"spam" + assert Foo.pb(foo).baz == b"" def test_bytes_rmw(): @@ -35,27 +35,27 @@ class Foo(proto.Message): spam = proto.Field(proto.BYTES, number=1) eggs = proto.Field(proto.BYTES, number=2) - foo = Foo(spam=b'bar') - foo.eggs = b'baz' - assert foo.spam == b'bar' - assert foo.eggs == b'baz' - assert Foo.pb(foo).spam == b'bar' - assert Foo.pb(foo).eggs == b'baz' - foo.spam = b'bacon' - assert foo.spam == b'bacon' - assert foo.eggs == b'baz' - assert Foo.pb(foo).spam == b'bacon' - assert Foo.pb(foo).eggs == b'baz' + foo = Foo(spam=b"bar") + foo.eggs = b"baz" + assert foo.spam == b"bar" + assert foo.eggs == b"baz" + assert Foo.pb(foo).spam == b"bar" + assert Foo.pb(foo).eggs == b"baz" + foo.spam = b"bacon" + assert foo.spam == b"bacon" + assert foo.eggs == b"baz" + assert Foo.pb(foo).spam == b"bacon" + assert Foo.pb(foo).eggs == b"baz" def test_bytes_del(): class Foo(proto.Message): bar = proto.Field(proto.BYTES, number=1) - foo = Foo(bar=b'spam') - assert foo.bar == b'spam' + foo = Foo(bar=b"spam") + assert foo.bar == b"spam" del foo.bar - assert foo.bar == b'' + assert foo.bar == b"" assert not foo.bar @@ -69,7 +69,7 @@ class Foo(proto.Message): # Since protobuf was written against Python 2, it accepts bytes objects # for strings (but not vice versa). - foo.bar = b'anything' - assert foo.bar == 'anything' + foo.bar = b"anything" + assert foo.bar == "anything" with pytest.raises(TypeError): - foo.baz = 'anything' + foo.baz = "anything" diff --git a/packages/proto-plus/tests/test_fields_composite.py b/packages/proto-plus/tests/test_fields_composite.py index fbcaa594df26..60d29e122b12 100644 --- a/packages/proto-plus/tests/test_fields_composite.py +++ b/packages/proto-plus/tests/test_fields_composite.py @@ -24,8 +24,8 @@ class Spam(proto.Message): foo = proto.Field(proto.MESSAGE, number=1, message=Foo) eggs = proto.Field(proto.BOOL, number=2) - spam = Spam(foo=Foo(bar='str', baz=42)) - assert spam.foo.bar == 'str' + spam = Spam(foo=Foo(bar="str", baz=42)) + assert spam.foo.bar == "str" assert spam.foo.baz == 42 assert spam.eggs is False @@ -37,10 +37,10 @@ class Foo(proto.Message): class Spam(proto.Message): foo = proto.Field(proto.MESSAGE, number=1, message=Foo) - spam = Spam(foo=Foo(bar='str')) - spam.foo.bar = 'other str' - assert spam.foo.bar == 'other str' - assert Spam.pb(spam).foo.bar == 'other str' + spam = Spam(foo=Foo(bar="str")) + spam.foo.bar = "other str" + assert spam.foo.bar == "other str" + assert Spam.pb(spam).foo.bar == "other str" def test_composite_empty_inner_rmw(): @@ -75,7 +75,7 @@ class Spam(proto.Message): foo = proto.Field(proto.MESSAGE, number=1, message=Foo) spam = Spam() - spam.foo = {'bar': 2.71828} + spam.foo = {"bar": 2.71828} assert abs(spam.foo.bar - 2.71828) < 1e-7 @@ -86,8 +86,8 @@ class Foo(proto.Message): class Spam(proto.Message): foo = proto.Field(proto.MESSAGE, number=1, message=Foo) - spam = Spam(foo=Foo(bar='str')) + spam = Spam(foo=Foo(bar="str")) del spam.foo assert not spam.foo assert isinstance(spam.foo, Foo) - assert spam.foo.bar == '' + assert spam.foo.bar == "" diff --git a/packages/proto-plus/tests/test_fields_composite_string_ref.py b/packages/proto-plus/tests/test_fields_composite_string_ref.py index f8df83441e20..b6c6faaa0a47 100644 --- a/packages/proto-plus/tests/test_fields_composite_string_ref.py +++ b/packages/proto-plus/tests/test_fields_composite_string_ref.py @@ -19,39 +19,41 @@ def test_composite_forward_ref(): class Spam(proto.Message): - foo = proto.Field(proto.MESSAGE, number=1, message='Foo') + foo = proto.Field(proto.MESSAGE, number=1, message="Foo") eggs = proto.Field(proto.BOOL, number=2) class Foo(proto.Message): bar = proto.Field(proto.STRING, number=1) baz = proto.Field(proto.INT64, number=2) - spam = Spam(foo=Foo(bar='str', baz=42)) - assert spam.foo.bar == 'str' + spam = Spam(foo=Foo(bar="str", baz=42)) + assert spam.foo.bar == "str" assert spam.foo.baz == 42 assert spam.eggs is False def test_composite_forward_ref_with_package(): - sys.modules[__name__].__protobuf__ = proto.module(package='abc.def') + sys.modules[__name__].__protobuf__ = proto.module(package="abc.def") try: + class Spam(proto.Message): - foo = proto.Field('Foo', number=1) + foo = proto.Field("Foo", number=1) class Eggs(proto.Message): - foo = proto.Field('abc.def.Foo', number=1) + foo = proto.Field("abc.def.Foo", number=1) class Foo(proto.Message): bar = proto.Field(proto.STRING, number=1) baz = proto.Field(proto.INT64, number=2) + finally: del sys.modules[__name__].__protobuf__ - spam = Spam(foo=Foo(bar='str', baz=42)) - eggs = Eggs(foo=Foo(bar='rts', baz=24)) - assert spam.foo.bar == 'str' + spam = Spam(foo=Foo(bar="str", baz=42)) + eggs = Eggs(foo=Foo(bar="rts", baz=24)) + assert spam.foo.bar == "str" assert spam.foo.baz == 42 - assert eggs.foo.bar == 'rts' + assert eggs.foo.bar == "rts" assert eggs.foo.baz == 24 @@ -64,15 +66,15 @@ class Spam(proto.Message): foo = proto.Field(proto.MESSAGE, number=1, message=Foo) eggs = proto.Field(proto.BOOL, number=2) - spam = Spam(foo=Foo(bar='str', baz=42)) - assert spam.foo.bar == 'str' + spam = Spam(foo=Foo(bar="str", baz=42)) + assert spam.foo.bar == "str" assert spam.foo.baz == 42 assert spam.eggs is False def test_composite_multi_ref(): class Spam(proto.Message): - foo = proto.Field(proto.MESSAGE, number=1, message='Foo') + foo = proto.Field(proto.MESSAGE, number=1, message="Foo") eggs = proto.Field(proto.BOOL, number=2) class Foo(proto.Message): @@ -82,9 +84,9 @@ class Foo(proto.Message): class Bacon(proto.Message): foo = proto.Field(proto.MESSAGE, number=1, message=Foo) - spam = Spam(foo=Foo(bar='str', baz=42)) + spam = Spam(foo=Foo(bar="str", baz=42)) bacon = Bacon(foo=spam.foo) - assert spam.foo.bar == 'str' + assert spam.foo.bar == "str" assert spam.foo.baz == 42 assert spam.eggs is False assert bacon.foo == spam.foo @@ -92,7 +94,7 @@ class Bacon(proto.Message): def test_composite_self_ref(): class Spam(proto.Message): - spam = proto.Field(proto.MESSAGE, number=1, message='Spam') + spam = proto.Field(proto.MESSAGE, number=1, message="Spam") eggs = proto.Field(proto.BOOL, number=2) spam = Spam(spam=Spam(eggs=True)) diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index 21c79d218197..b494bd0c9adc 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -61,7 +61,7 @@ class Color(proto.Enum): class Foo(proto.Message): color = proto.Field(Color, number=1) - foo = Foo(color='RED') + foo = Foo(color="RED") assert foo.color == Color.RED assert foo.color == 1 assert foo.color @@ -79,7 +79,7 @@ class Color(proto.Enum): class Foo(proto.Message): color = proto.Field(Color, number=1) - foo = Foo({'color': 1}) + foo = Foo({"color": 1}) assert foo.color == Color.RED assert foo.color == 1 assert foo.color @@ -97,7 +97,7 @@ class Color(proto.Enum): class Foo(proto.Message): color = proto.Field(Color, number=1) - foo = Foo({'color': 'BLUE'}) + foo = Foo({"color": "BLUE"}) assert foo.color == Color.BLUE assert foo.color == 3 assert foo.color @@ -136,10 +136,10 @@ class Foo(proto.Message): foo = Foo() assert foo.color == Color.COLOR_UNSPECIFIED assert foo.color == 0 - assert 'color' not in foo + assert "color" not in foo assert not foo.color assert Foo.pb(foo).color == 0 - assert Foo.serialize(foo) == b'' + assert Foo.serialize(foo) == b"" def test_outer_enum_write(): @@ -190,7 +190,7 @@ class Foo(proto.Message): color = proto.Field(Color, number=1) foo = Foo() - foo.color = 'BLUE' + foo.color = "BLUE" assert foo.color == Color.BLUE assert foo.color == 3 assert isinstance(foo.color, Color) @@ -249,6 +249,6 @@ class Foo(proto.Message): assert foo.color == Color.COLOR_UNSPECIFIED assert foo.color == 0 assert isinstance(foo.color, Color) - assert 'color' not in foo + assert "color" not in foo assert not foo.color assert Foo.pb(foo).color == 0 diff --git a/packages/proto-plus/tests/test_fields_int.py b/packages/proto-plus/tests/test_fields_int.py index 03614388e8b8..c3a979c052ca 100644 --- a/packages/proto-plus/tests/test_fields_int.py +++ b/packages/proto-plus/tests/test_fields_int.py @@ -91,5 +91,5 @@ def test_field_descriptor_idempotent(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) - bar_field = Foo.meta.fields['bar'] + bar_field = Foo.meta.fields["bar"] assert bar_field.descriptor is bar_field.descriptor diff --git a/packages/proto-plus/tests/test_fields_map_composite.py b/packages/proto-plus/tests/test_fields_map_composite.py index 696fa1c78e28..13bf0c5f6bf2 100644 --- a/packages/proto-plus/tests/test_fields_map_composite.py +++ b/packages/proto-plus/tests/test_fields_map_composite.py @@ -22,15 +22,13 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) - baz = Baz(foos={'i': Foo(bar=42), 'j': Foo(bar=24)}) + baz = Baz(foos={"i": Foo(bar=42), "j": Foo(bar=24)}) assert len(baz.foos) == 2 - assert baz.foos['i'].bar == 42 - assert baz.foos['j'].bar == 24 - assert 'k' not in baz.foos + assert baz.foos["i"].bar == 42 + assert baz.foos["j"].bar == 24 + assert "k" not in baz.foos def test_composite_map_dict(): @@ -38,17 +36,15 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) - baz = Baz(foos={'i': {'bar': 42}, 'j': {'bar': 24}}) + baz = Baz(foos={"i": {"bar": 42}, "j": {"bar": 24}}) assert len(baz.foos) == 2 - assert baz.foos['i'].bar == 42 - assert baz.foos['j'].bar == 24 - assert 'k' not in baz.foos + assert baz.foos["i"].bar == 42 + assert baz.foos["j"].bar == 24 + assert "k" not in baz.foos with pytest.raises(KeyError): - baz.foos['k'] + baz.foos["k"] def test_composite_map_set(): @@ -56,17 +52,15 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz() - baz.foos['i'] = Foo(bar=42) - baz.foos['j'] = Foo(bar=24) + baz.foos["i"] = Foo(bar=42) + baz.foos["j"] = Foo(bar=24) assert len(baz.foos) == 2 - assert baz.foos['i'].bar == 42 - assert baz.foos['j'].bar == 24 - assert 'k' not in baz.foos + assert baz.foos["i"].bar == 42 + assert baz.foos["j"].bar == 24 + assert "k" not in baz.foos def test_composite_map_deep_set(): @@ -74,15 +68,13 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz() - baz.foos['i'] = Foo() - baz.foos['i'].bar = 42 + baz.foos["i"] = Foo() + baz.foos["i"].bar = 42 assert len(baz.foos) == 1 - assert baz.foos['i'].bar == 42 + assert baz.foos["i"].bar == 42 def test_composite_map_del(): @@ -90,13 +82,11 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz() - baz.foos['i'] = Foo(bar=42) + baz.foos["i"] = Foo(bar=42) assert len(baz.foos) == 1 - del baz.foos['i'] + del baz.foos["i"] assert len(baz.foos) == 0 - assert 'i' not in baz.foos + assert "i" not in baz.foos diff --git a/packages/proto-plus/tests/test_fields_map_scalar.py b/packages/proto-plus/tests/test_fields_map_scalar.py index 27eeed74ae08..25ce495629b4 100644 --- a/packages/proto-plus/tests/test_fields_map_scalar.py +++ b/packages/proto-plus/tests/test_fields_map_scalar.py @@ -19,40 +19,40 @@ def test_basic_map(): class Foo(proto.Message): tags = proto.MapField(proto.STRING, proto.STRING, number=1) - foo = Foo(tags={'a': 'foo', 'b': 'bar'}) - assert foo.tags['a'] == 'foo' - assert foo.tags['b'] == 'bar' - assert 'c' not in foo.tags + foo = Foo(tags={"a": "foo", "b": "bar"}) + assert foo.tags["a"] == "foo" + assert foo.tags["b"] == "bar" + assert "c" not in foo.tags def test_basic_map_with_underscore_field_name(): class Foo(proto.Message): tag_labels = proto.MapField(proto.STRING, proto.STRING, number=1) - foo = Foo(tag_labels={'a': 'foo', 'b': 'bar'}) - assert foo.tag_labels['a'] == 'foo' - assert foo.tag_labels['b'] == 'bar' - assert 'c' not in foo.tag_labels + foo = Foo(tag_labels={"a": "foo", "b": "bar"}) + assert foo.tag_labels["a"] == "foo" + assert foo.tag_labels["b"] == "bar" + assert "c" not in foo.tag_labels def test_basic_map_assignment(): class Foo(proto.Message): tags = proto.MapField(proto.STRING, proto.STRING, number=1) - foo = Foo(tags={'a': 'foo'}) - foo.tags['b'] = 'bar' + foo = Foo(tags={"a": "foo"}) + foo.tags["b"] = "bar" assert len(foo.tags) == 2 - assert foo.tags['a'] == 'foo' - assert foo.tags['b'] == 'bar' - assert 'c' not in foo.tags + assert foo.tags["a"] == "foo" + assert foo.tags["b"] == "bar" + assert "c" not in foo.tags def test_basic_map_deletion(): class Foo(proto.Message): tags = proto.MapField(proto.STRING, proto.STRING, number=1) - foo = Foo(tags={'a': 'foo', 'b': 'bar'}) - del foo.tags['b'] + foo = Foo(tags={"a": "foo", "b": "bar"}) + del foo.tags["b"] assert len(foo.tags) == 1 - assert foo.tags['a'] == 'foo' - assert 'b' not in foo.tags + assert foo.tags["a"] == "foo" + assert "b" not in foo.tags diff --git a/packages/proto-plus/tests/test_fields_oneof.py b/packages/proto-plus/tests/test_fields_oneof.py index 8f0d12446b9a..63a82764e79e 100644 --- a/packages/proto-plus/tests/test_fields_oneof.py +++ b/packages/proto-plus/tests/test_fields_oneof.py @@ -17,23 +17,23 @@ def test_oneof(): class Foo(proto.Message): - bar = proto.Field(proto.INT32, number=1, oneof='bacon') - baz = proto.Field(proto.STRING, number=2, oneof='bacon') + bar = proto.Field(proto.INT32, number=1, oneof="bacon") + baz = proto.Field(proto.STRING, number=2, oneof="bacon") foo = Foo(bar=42) assert foo.bar == 42 assert not foo.baz - foo.baz = 'the answer' + foo.baz = "the answer" assert not foo.bar - assert foo.baz == 'the answer' + assert foo.baz == "the answer" def test_multiple_oneofs(): class Foo(proto.Message): - bar = proto.Field(proto.INT32, number=1, oneof='spam') - baz = proto.Field(proto.STRING, number=2, oneof='spam') - bacon = proto.Field(proto.FLOAT, number=3, oneof='eggs') - ham = proto.Field(proto.STRING, number=4, oneof='eggs') + bar = proto.Field(proto.INT32, number=1, oneof="spam") + baz = proto.Field(proto.STRING, number=2, oneof="spam") + bacon = proto.Field(proto.FLOAT, number=3, oneof="eggs") + ham = proto.Field(proto.STRING, number=4, oneof="eggs") foo = Foo() foo.bar = 42 @@ -42,8 +42,8 @@ class Foo(proto.Message): assert foo.bacon == 42.0 assert not foo.baz assert not foo.ham - foo.ham = 'this one gets assigned' + foo.ham = "this one gets assigned" assert not foo.bacon - assert foo.ham == 'this one gets assigned' + assert foo.ham == "this one gets assigned" assert foo.bar == 42 assert not foo.baz diff --git a/packages/proto-plus/tests/test_fields_optional.py b/packages/proto-plus/tests/test_fields_optional.py index 311cdfff9064..a15904c1a59d 100644 --- a/packages/proto-plus/tests/test_fields_optional.py +++ b/packages/proto-plus/tests/test_fields_optional.py @@ -45,8 +45,8 @@ def test_optional_and_oneof(): # Oneof defined before an optional class Squid(proto.Message): - mass_kg = proto.Field(proto.INT32, number=1, oneof='mass') - mass_lbs = proto.Field(proto.INT32, number=2, oneof='mass') + mass_kg = proto.Field(proto.INT32, number=1, oneof="mass") + mass_lbs = proto.Field(proto.INT32, number=2, oneof="mass") iridiphore_num = proto.Field(proto.INT32, number=3, optional=True) @@ -69,8 +69,8 @@ class Squid(proto.Message): class Clam(proto.Message): flute_radius = proto.Field(proto.INT32, number=1, optional=True) - mass_kg = proto.Field(proto.INT32, number=2, oneof='mass') - mass_lbs = proto.Field(proto.INT32, number=3, oneof='mass') + mass_kg = proto.Field(proto.INT32, number=2, oneof="mass") + mass_lbs = proto.Field(proto.INT32, number=3, oneof="mass") c = Clam(mass_kg=20) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index 8f12604c4ae6..34b3933af46b 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -53,7 +53,7 @@ class Foo(proto.Message): class Baz(proto.Message): foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) - baz = Baz(foos=[{'bar': 42}]) + baz = Baz(foos=[{"bar": 42}]) assert len(baz.foos) == 1 assert baz.foos[0].bar == 42 @@ -72,17 +72,15 @@ class Baz(proto.Message): def test_repeated_composite_marshaled(): class Foo(proto.Message): - timestamps = proto.RepeatedField(proto.MESSAGE, - message=timestamp_pb2.Timestamp, - number=1, + timestamps = proto.RepeatedField( + proto.MESSAGE, message=timestamp_pb2.Timestamp, number=1, ) foo = Foo(timestamps=[datetime(2012, 4, 21, 15, tzinfo=timezone.utc)]) foo.timestamps.append(timestamp_pb2.Timestamp(seconds=86400 * 365)) foo.timestamps.append(datetime(2017, 10, 14, tzinfo=timezone.utc)) assert all([isinstance(i, datetime) for i in foo.timestamps]) - assert all([isinstance(i, timestamp_pb2.Timestamp) - for i in Foo.pb(foo).timestamps]) + assert all([isinstance(i, timestamp_pb2.Timestamp) for i in Foo.pb(foo).timestamps]) assert foo.timestamps[0].year == 2012 assert foo.timestamps[0].month == 4 assert foo.timestamps[0].hour == 15 @@ -117,7 +115,7 @@ class Baz(proto.Message): baz = Baz() baz.foos.append(Foo(bar=96)) - baz.foos.append({'bar': 72}) + baz.foos.append({"bar": 72}) assert len(baz.foos) == 2 assert baz.foos[0].bar == 96 assert baz.foos[1].bar == 72 @@ -131,7 +129,7 @@ class Baz(proto.Message): foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) baz = Baz() - baz.foos.insert(0, {'bar': 72}) + baz.foos.insert(0, {"bar": 72}) baz.foos.insert(0, Foo(bar=96)) assert len(baz.foos) == 2 assert baz.foos[0].bar == 96 @@ -159,7 +157,7 @@ class Foo(proto.Message): class Baz(proto.Message): foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) - baz = Baz(foos=[{'bar': 96}, {'bar': 48}]) + baz = Baz(foos=[{"bar": 96}, {"bar": 48}]) baz.foos[1] = Foo(bar=55) assert baz.foos[0].bar == 96 assert baz.foos[1].bar == 55 diff --git a/packages/proto-plus/tests/test_fields_repeated_scalar.py b/packages/proto-plus/tests/test_fields_repeated_scalar.py index ea171892c1ab..e07cf1306f0e 100644 --- a/packages/proto-plus/tests/test_fields_repeated_scalar.py +++ b/packages/proto-plus/tests/test_fields_repeated_scalar.py @@ -111,4 +111,4 @@ class Foo(proto.Message): with pytest.raises(TypeError): foo.bar.append(21.0) with pytest.raises(TypeError): - foo.bar.append('21') + foo.bar.append("21") diff --git a/packages/proto-plus/tests/test_fields_string.py b/packages/proto-plus/tests/test_fields_string.py index eadbbd286da6..6fd15c7311da 100644 --- a/packages/proto-plus/tests/test_fields_string.py +++ b/packages/proto-plus/tests/test_fields_string.py @@ -20,12 +20,12 @@ class Foo(proto.Message): bar = proto.Field(proto.STRING, number=1) baz = proto.Field(proto.STRING, number=2) - foo = Foo(bar='spam') - assert foo.bar == 'spam' - assert foo.baz == '' + foo = Foo(bar="spam") + assert foo.bar == "spam" + assert foo.baz == "" assert not foo.baz - assert Foo.pb(foo).bar == 'spam' - assert Foo.pb(foo).baz == '' + assert Foo.pb(foo).bar == "spam" + assert Foo.pb(foo).baz == "" def test_string_rmw(): @@ -33,25 +33,25 @@ class Foo(proto.Message): spam = proto.Field(proto.STRING, number=1) eggs = proto.Field(proto.STRING, number=2) - foo = Foo(spam='bar') - foo.eggs = 'baz' - assert foo.spam == 'bar' - assert foo.eggs == 'baz' - assert Foo.pb(foo).spam == 'bar' - assert Foo.pb(foo).eggs == 'baz' - foo.spam = 'bacon' - assert foo.spam == 'bacon' - assert foo.eggs == 'baz' - assert Foo.pb(foo).spam == 'bacon' - assert Foo.pb(foo).eggs == 'baz' + foo = Foo(spam="bar") + foo.eggs = "baz" + assert foo.spam == "bar" + assert foo.eggs == "baz" + assert Foo.pb(foo).spam == "bar" + assert Foo.pb(foo).eggs == "baz" + foo.spam = "bacon" + assert foo.spam == "bacon" + assert foo.eggs == "baz" + assert Foo.pb(foo).spam == "bacon" + assert Foo.pb(foo).eggs == "baz" def test_string_del(): class Foo(proto.Message): bar = proto.Field(proto.STRING, number=1) - foo = Foo(bar='spam') - assert foo.bar == 'spam' + foo = Foo(bar="spam") + assert foo.bar == "spam" del foo.bar - assert foo.bar == '' + assert foo.bar == "" assert not foo.bar diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index f3176ddce19e..a300a84b8f26 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -32,10 +32,10 @@ def test_message_from_json(): class Squid(proto.Message): mass_kg = proto.Field(proto.INT32, number=1) - json = '''{ + json = """{ "massKg": 100 } - ''' + """ s = Squid.from_json(json) assert s == Squid(mass_kg=100) diff --git a/packages/proto-plus/tests/test_marshal_register.py b/packages/proto-plus/tests/test_marshal_register.py index 673355e1e3f3..8a9597f38a73 100644 --- a/packages/proto-plus/tests/test_marshal_register.py +++ b/packages/proto-plus/tests/test_marshal_register.py @@ -29,12 +29,14 @@ def to_proto(self, value): def to_python(self, value, *, absent=None): return value + assert isinstance(marshal._rules[empty_pb2.Empty], Rule) def test_invalid_target_registration(): marshal = BaseMarshal() with pytest.raises(TypeError): + @marshal.register(object) class Rule: def to_proto(self, value): @@ -47,6 +49,7 @@ def to_python(self, value, *, absent=None): def test_invalid_marshal_class(): marshal = BaseMarshal() with pytest.raises(TypeError): + @marshal.register(empty_pb2.Empty) class Marshal: pass diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 22264c54ff3a..991633b59965 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -25,10 +25,10 @@ def test_timestamp_read(): class Foo(proto.Message): - event_time = proto.Field(proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + event_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) + foo = Foo(event_time=timestamp_pb2.Timestamp(seconds=1335020400)) assert isinstance(foo.event_time, datetime) assert foo.event_time.year == 2012 @@ -41,10 +41,10 @@ class Foo(proto.Message): def test_timestamp_write_init(): class Foo(proto.Message): - event_time = proto.Field(proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + event_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) + foo = Foo(event_time=datetime(2012, 4, 21, 15, tzinfo=timezone.utc)) assert isinstance(foo.event_time, datetime) assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) @@ -56,10 +56,10 @@ class Foo(proto.Message): def test_timestamp_write(): class Foo(proto.Message): - event_time = proto.Field(proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + event_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) + foo = Foo() foo.event_time = datetime(2012, 4, 21, 15, tzinfo=timezone.utc) assert isinstance(foo.event_time, datetime) @@ -72,10 +72,10 @@ class Foo(proto.Message): def test_timestamp_write_pb2(): class Foo(proto.Message): - event_time = proto.Field(proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + event_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) + foo = Foo() foo.event_time = timestamp_pb2.Timestamp(seconds=1335020400) assert isinstance(foo.event_time, datetime) @@ -88,10 +88,10 @@ class Foo(proto.Message): def test_timestamp_rmw_nanos(): class Foo(proto.Message): - event_time = proto.Field(proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + event_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) + foo = Foo() foo.event_time = datetime(2012, 4, 21, 15, 0, 0, 1, tzinfo=timezone.utc) assert foo.event_time.microsecond == 1 @@ -103,20 +103,20 @@ class Foo(proto.Message): def test_timestamp_absence(): class Foo(proto.Message): - event_time = proto.Field(proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + event_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) + foo = Foo() assert foo.event_time is None def test_timestamp_del(): class Foo(proto.Message): - event_time = proto.Field(proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + event_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) + foo = Foo(event_time=datetime(2012, 4, 21, 15, tzinfo=timezone.utc)) del foo.event_time assert foo.event_time is None @@ -124,10 +124,8 @@ class Foo(proto.Message): def test_duration_read(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + foo = Foo(ttl=duration_pb2.Duration(seconds=60, nanos=1000)) assert isinstance(foo.ttl, timedelta) assert isinstance(Foo.pb(foo).ttl, duration_pb2.Duration) @@ -138,10 +136,8 @@ class Foo(proto.Message): def test_duration_write_init(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + foo = Foo(ttl=timedelta(days=2)) assert isinstance(foo.ttl, timedelta) assert isinstance(Foo.pb(foo).ttl, duration_pb2.Duration) @@ -153,10 +149,8 @@ class Foo(proto.Message): def test_duration_write(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + foo = Foo() foo.ttl = timedelta(seconds=120) assert isinstance(foo.ttl, timedelta) @@ -167,10 +161,8 @@ class Foo(proto.Message): def test_duration_write_pb2(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + foo = Foo() foo.ttl = duration_pb2.Duration(seconds=120) assert isinstance(foo.ttl, timedelta) @@ -181,10 +173,8 @@ class Foo(proto.Message): def test_duration_del(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + foo = Foo(ttl=timedelta(seconds=900)) del foo.ttl assert isinstance(foo.ttl, timedelta) @@ -195,10 +185,8 @@ class Foo(proto.Message): def test_duration_nanos_rmw(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + foo = Foo(ttl=timedelta(microseconds=50)) assert foo.ttl.microseconds == 50 assert Foo.pb(foo).ttl.nanos == 50000 diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py index 7127b579b83d..129f70161607 100644 --- a/packages/proto-plus/tests/test_marshal_types_enum.py +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -28,7 +28,7 @@ class Foo(proto.Enum): enum_rule = EnumRule(Foo) foo_a = enum_rule.to_proto(Foo.BAR) foo_b = enum_rule.to_proto(1) - foo_c = enum_rule.to_proto('BAR') + foo_c = enum_rule.to_proto("BAR") # We want to distinguish literal `1` from `Foo.BAR` here # (they are equivalent but not identical). assert foo_a is foo_b is foo_c is 1 # noqa: F632 @@ -53,6 +53,6 @@ class Foo(proto.Enum): BAZ = 2 enum_rule = EnumRule(Foo) - with mock.patch.object(warnings, 'warn') as warn: + with mock.patch.object(warnings, "warn") as warn: assert enum_rule.to_python(4) == 4 - warn.assert_called_once_with('Unrecognized Foo enum value: 4') + warn.assert_called_once_with("Unrecognized Foo enum value: 4") diff --git a/packages/proto-plus/tests/test_marshal_types_message.py b/packages/proto-plus/tests/test_marshal_types_message.py index 49d48e4dd7a8..b96ec32285d8 100644 --- a/packages/proto-plus/tests/test_marshal_types_message.py +++ b/packages/proto-plus/tests/test_marshal_types_message.py @@ -23,7 +23,7 @@ class Foo(proto.Message): message_rule = MessageRule(Foo.pb(), Foo) foo_pb2_a = message_rule.to_proto(Foo(bar=42)) foo_pb2_b = message_rule.to_proto(Foo.pb()(bar=42)) - foo_pb2_c = message_rule.to_proto({'bar': 42}) + foo_pb2_c = message_rule.to_proto({"bar": 42}) assert foo_pb2_a == foo_pb2_b == foo_pb2_c diff --git a/packages/proto-plus/tests/test_marshal_types_struct.py b/packages/proto-plus/tests/test_marshal_types_struct.py index 40f709b4c3eb..74150f6ec011 100644 --- a/packages/proto-plus/tests/test_marshal_types_struct.py +++ b/packages/proto-plus/tests/test_marshal_types_struct.py @@ -24,7 +24,7 @@ class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) assert Foo(value=3).value == 3.0 - assert Foo(value='3').value == '3' + assert Foo(value="3").value == "3" assert Foo(value=None).value is None assert Foo(value=False).value is False assert Foo(value=True).value is True @@ -41,37 +41,37 @@ class Foo(proto.Message): foo.value = False assert not foo.value assert foo.value is False - assert Foo.pb(foo).value.WhichOneof('kind') == 'bool_value' + assert Foo.pb(foo).value.WhichOneof("kind") == "bool_value" foo.value = None assert not foo.value assert foo.value is None - assert Foo.pb(foo).value.WhichOneof('kind') == 'null_value' + assert Foo.pb(foo).value.WhichOneof("kind") == "null_value" def test_value_write_pb(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value=struct_pb2.Value(string_value='stringy')) - assert foo.value == 'stringy' + foo = Foo(value=struct_pb2.Value(string_value="stringy")) + assert foo.value == "stringy" def test_value_lists_read(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value=['3', None, 'foo', True]) - assert foo.value == ['3', None, 'foo', True] + foo = Foo(value=["3", None, "foo", True]) + assert foo.value == ["3", None, "foo", True] def test_value_lists_rmw(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value=['3', None, 'foo', True]) - foo.value.append('bar') + foo = Foo(value=["3", None, "foo", True]) + foo.value.append("bar") foo.value.pop(1) - assert foo.value == ['3', 'foo', True, 'bar'] + assert foo.value == ["3", "foo", True, "bar"] def test_value_lists_nested(): @@ -87,46 +87,46 @@ def test_value_lists_struct(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value=[{'foo': 'bar', 'spam': 'eggs'}]) - assert foo.value == [{'foo': 'bar', 'spam': 'eggs'}] + foo = Foo(value=[{"foo": "bar", "spam": "eggs"}]) + assert foo.value == [{"foo": "bar", "spam": "eggs"}] def test_value_lists_detachment(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value=['foo', 'bar']) + foo = Foo(value=["foo", "bar"]) detached_list = foo.value - detached_list.append('baz') - assert foo.value == ['foo', 'bar', 'baz'] + detached_list.append("baz") + assert foo.value == ["foo", "bar", "baz"] def test_value_structs_read(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value={'foo': True, 'bar': False}) - assert foo.value == {'foo': True, 'bar': False} + foo = Foo(value={"foo": True, "bar": False}) + assert foo.value == {"foo": True, "bar": False} def test_value_structs_rmw(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value={'foo': True, 'bar': False}) - foo.value['baz'] = 'a string' - assert foo.value == {'foo': True, 'bar': False, 'baz': 'a string'} + foo = Foo(value={"foo": True, "bar": False}) + foo.value["baz"] = "a string" + assert foo.value == {"foo": True, "bar": False, "baz": "a string"} def test_value_structs_nested(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) - foo = Foo(value={'foo': True, 'bar': {'spam': 'eggs'}}) - assert foo.value == {'foo': True, 'bar': {'spam': 'eggs'}} + foo = Foo(value={"foo": True, "bar": {"spam": "eggs"}}) + assert foo.value == {"foo": True, "bar": {"spam": "eggs"}} sv = Foo.pb(foo).value.struct_value - assert sv['foo'] is True - assert sv['bar'].fields['spam'].string_value == 'eggs' + assert sv["foo"] is True + assert sv["bar"].fields["spam"].string_value == "eggs" def test_value_invalid_value(): @@ -142,38 +142,42 @@ class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) foo = Foo() - assert not hasattr(foo, 'value') + assert not hasattr(foo, "value") def test_list_value_read(): class Foo(proto.Message): value = proto.Field(struct_pb2.ListValue, number=1) - foo = Foo(value=['foo', 'bar', True, {'spam': 'eggs'}]) - assert foo.value == ['foo', 'bar', True, {'spam': 'eggs'}] + foo = Foo(value=["foo", "bar", True, {"spam": "eggs"}]) + assert foo.value == ["foo", "bar", True, {"spam": "eggs"}] def test_list_value_pb(): class Foo(proto.Message): value = proto.Field(struct_pb2.ListValue, number=1) - foo = Foo(value=struct_pb2.ListValue(values=[ - struct_pb2.Value(string_value='foo'), - struct_pb2.Value(string_value='bar'), - struct_pb2.Value(bool_value=True), - ])) - assert foo.value == ['foo', 'bar', True] + foo = Foo( + value=struct_pb2.ListValue( + values=[ + struct_pb2.Value(string_value="foo"), + struct_pb2.Value(string_value="bar"), + struct_pb2.Value(bool_value=True), + ] + ) + ) + assert foo.value == ["foo", "bar", True] def test_list_value_reassignment(): class Foo(proto.Message): value = proto.Field(struct_pb2.ListValue, number=1) - foo = Foo(value=['foo', 'bar']) + foo = Foo(value=["foo", "bar"]) detached = foo.value detached.append(True) foo.value = detached - assert foo.value == ['foo', 'bar', True] + assert foo.value == ["foo", "bar", True] def test_list_value_invalid(): @@ -188,27 +192,31 @@ def test_struct_read(): class Foo(proto.Message): value = proto.Field(struct_pb2.Struct, number=1) - foo = Foo(value={'foo': 'bar', 'bacon': True}) - assert foo.value == {'foo': 'bar', 'bacon': True} + foo = Foo(value={"foo": "bar", "bacon": True}) + assert foo.value == {"foo": "bar", "bacon": True} def test_struct_pb(): class Foo(proto.Message): value = proto.Field(struct_pb2.Struct, number=1) - foo = Foo(value=struct_pb2.Struct(fields={ - 'foo': struct_pb2.Value(string_value='bar'), - 'bacon': struct_pb2.Value(bool_value=True), - })) - assert foo.value == {'foo': 'bar', 'bacon': True} + foo = Foo( + value=struct_pb2.Struct( + fields={ + "foo": struct_pb2.Value(string_value="bar"), + "bacon": struct_pb2.Value(bool_value=True), + } + ) + ) + assert foo.value == {"foo": "bar", "bacon": True} def test_struct_reassignment(): class Foo(proto.Message): value = proto.Field(struct_pb2.Struct, number=1) - foo = Foo(value={'foo': 'bar'}) + foo = Foo(value={"foo": "bar"}) detached = foo.value - detached['bacon'] = True + detached["bacon"] = True foo.value = detached - assert foo.value == {'foo': 'bar', 'bacon': True} + assert foo.value == {"foo": "bar", "bacon": True} diff --git a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py index 0a25a48c8768..ffe206a696f9 100644 --- a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py +++ b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py @@ -20,10 +20,8 @@ def test_bool_value_init(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + assert Foo(bar=True).bar is True assert Foo(bar=False).bar is False assert Foo().bar is None @@ -31,22 +29,18 @@ class Foo(proto.Message): def test_bool_value_init_dict(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) - assert Foo({'bar': True}).bar is True - assert Foo({'bar': False}).bar is False - assert Foo({'bar': None}).bar is None + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + + assert Foo({"bar": True}).bar is True + assert Foo({"bar": False}).bar is False + assert Foo({"bar": None}).bar is None def test_bool_value_distinction_from_bool(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) baz = proto.Field(proto.BOOL, number=2) + assert Foo().bar is None assert Foo().baz is False @@ -55,6 +49,7 @@ def test_bool_value_rmw(): class Foo(proto.Message): bar = proto.Field(wrappers_pb2.BoolValue, number=1) baz = proto.Field(wrappers_pb2.BoolValue, number=2) + foo = Foo(bar=False) assert foo.bar is False assert foo.baz is None @@ -63,15 +58,13 @@ class Foo(proto.Message): assert Foo.pb(foo).baz.value is True foo.bar = None assert foo.bar is None - assert not Foo.pb(foo).HasField('bar') + assert not Foo.pb(foo).HasField("bar") def test_bool_value_write_bool_value(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + foo = Foo(bar=True) foo.bar = wrappers_pb2.BoolValue() assert foo.bar is False @@ -79,10 +72,8 @@ class Foo(proto.Message): def test_bool_value_del(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + foo = Foo(bar=False) assert foo.bar is False del foo.bar @@ -93,6 +84,7 @@ def test_multiple_types(): class Foo(proto.Message): bar = proto.Field(wrappers_pb2.BoolValue, number=1) baz = proto.Field(wrappers_pb2.Int32Value, number=2) + foo = Foo(bar=True, baz=42) assert foo.bar is True assert foo.baz == 42 diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index c6520c197caa..de0fd881d630 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -20,6 +20,7 @@ def test_message_constructor_instance(): class Foo(proto.Message): bar = proto.Field(proto.INT64, number=1) + foo_original = Foo(bar=42) foo_copy = Foo(foo_original) assert foo_original.bar == foo_copy.bar == 42 @@ -33,6 +34,7 @@ class Foo(proto.Message): def test_message_constructor_underlying_pb2(): class Foo(proto.Message): bar = proto.Field(proto.INT64, number=1) + foo_pb2 = Foo.pb()(bar=42) foo = Foo(foo_pb2) assert foo.bar == Foo.pb(foo).bar == foo_pb2.bar == 42 @@ -47,6 +49,7 @@ class Foo(proto.Message): def test_message_constructor_underlying_pb2_and_kwargs(): class Foo(proto.Message): bar = proto.Field(proto.INT64, number=1) + foo_pb2 = Foo.pb()(bar=42) foo = Foo(foo_pb2, bar=99) assert foo.bar == Foo.pb(foo).bar == 99 @@ -59,9 +62,10 @@ class Foo(proto.Message): def test_message_constructor_dict(): class Foo(proto.Message): bar = proto.Field(proto.INT64, number=1) - foo = Foo({'bar': 42}) + + foo = Foo({"bar": 42}) assert foo.bar == Foo.pb(foo).bar == 42 - assert foo != {'bar': 42} + assert foo != {"bar": 42} assert isinstance(foo, Foo) assert isinstance(Foo.pb(foo), Foo.pb()) @@ -69,6 +73,7 @@ class Foo(proto.Message): def test_message_constructor_kwargs(): class Foo(proto.Message): bar = proto.Field(proto.INT64, number=1) + foo = Foo(bar=42) assert foo.bar == Foo.pb(foo).bar == 42 assert isinstance(foo, Foo) @@ -78,14 +83,16 @@ class Foo(proto.Message): def test_message_constructor_invalid(): class Foo(proto.Message): bar = proto.Field(proto.INT64, number=1) + with pytest.raises(TypeError): Foo(object()) def test_message_constructor_explicit_qualname(): class Foo(proto.Message): - __qualname__ = 'Foo' + __qualname__ = "Foo" bar = proto.Field(proto.INT64, number=1) + foo_original = Foo(bar=42) foo_copy = Foo(foo_original) assert foo_original.bar == foo_copy.bar == 42 @@ -99,9 +106,10 @@ class Foo(proto.Message): def test_message_contains_primitive(): class Foo(proto.Message): bar = proto.Field(proto.INT64, number=1) - assert 'bar' in Foo(bar=42) - assert 'bar' not in Foo(bar=0) - assert 'bar' not in Foo() + + assert "bar" in Foo(bar=42) + assert "bar" not in Foo(bar=0) + assert "bar" not in Foo() def test_message_contains_composite(): @@ -111,19 +119,19 @@ class Foo(proto.Message): class Baz(proto.Message): foo = proto.Field(proto.MESSAGE, number=1, message=Foo) - assert 'foo' in Baz(foo=Foo(bar=42)) - assert 'foo' in Baz(foo=Foo()) - assert 'foo' not in Baz() + assert "foo" in Baz(foo=Foo(bar=42)) + assert "foo" in Baz(foo=Foo()) + assert "foo" not in Baz() def test_message_contains_repeated_primitive(): class Foo(proto.Message): bar = proto.RepeatedField(proto.INT64, number=1) - assert 'bar' in Foo(bar=[1, 1, 2, 3, 5]) - assert 'bar' in Foo(bar=[0]) - assert 'bar' not in Foo(bar=[]) - assert 'bar' not in Foo() + assert "bar" in Foo(bar=[1, 1, 2, 3, 5]) + assert "bar" in Foo(bar=[0]) + assert "bar" not in Foo(bar=[]) + assert "bar" not in Foo() def test_message_contains_repeated_composite(): @@ -133,10 +141,10 @@ class Foo(proto.Message): class Baz(proto.Message): foo = proto.RepeatedField(proto.MESSAGE, number=1, message=Foo) - assert 'foo' in Baz(foo=[Foo(bar=42)]) - assert 'foo' in Baz(foo=[Foo()]) - assert 'foo' not in Baz(foo=[]) - assert 'foo' not in Baz() + assert "foo" in Baz(foo=[Foo(bar=42)]) + assert "foo" in Baz(foo=[Foo()]) + assert "foo" not in Baz(foo=[]) + assert "foo" not in Baz() def test_message_eq_primitives(): @@ -146,17 +154,17 @@ class Foo(proto.Message): bacon = proto.Field(proto.BOOL, number=3) assert Foo() == Foo() - assert Foo(bar=42, baz='42') == Foo(bar=42, baz='42') - assert Foo(bar=42, baz='42') != Foo(baz='42') + assert Foo(bar=42, baz="42") == Foo(bar=42, baz="42") + assert Foo(bar=42, baz="42") != Foo(baz="42") assert Foo(bar=42, bacon=True) == Foo(bar=42, bacon=True) assert Foo(bar=42, bacon=True) != Foo(bar=42) - assert Foo(bar=42, baz='42', bacon=True) != Foo(bar=42, bacon=True) + assert Foo(bar=42, baz="42", bacon=True) != Foo(bar=42, bacon=True) assert Foo(bacon=False) == Foo() assert Foo(bacon=True) != Foo(bacon=False) assert Foo(bar=21 * 2) == Foo(bar=42) assert Foo() == Foo(bar=0) - assert Foo() == Foo(bar=0, baz='', bacon=False) - assert Foo() != Foo(bar=0, baz='0', bacon=False) + assert Foo() == Foo(bar=0, baz="", bacon=False) + assert Foo() != Foo(bar=0, baz="0", bacon=False) def test_message_serialize(): @@ -175,7 +183,7 @@ class Foo(proto.Message): baz = proto.Field(proto.STRING, number=2) bacon = proto.Field(proto.BOOL, number=3) - foo = {'bar': 42, 'bacon': True} + foo = {"bar": 42, "bacon": True} assert Foo.serialize(foo) == Foo.pb(foo, coerce=True).SerializeToString() diff --git a/packages/proto-plus/tests/test_message_nested.py b/packages/proto-plus/tests/test_message_nested.py index e3cbbfd150ec..89dfad68610d 100644 --- a/packages/proto-plus/tests/test_message_nested.py +++ b/packages/proto-plus/tests/test_message_nested.py @@ -19,6 +19,7 @@ def test_singly_nested_message(): class Foo(proto.Message): class Bar(proto.Message): value = proto.Field(proto.INT32, number=1) + bar = proto.Field(proto.MESSAGE, number=1, message=Bar) foo = Foo(bar=Foo.Bar(value=42)) @@ -30,7 +31,9 @@ class Foo(proto.Message): class Bar(proto.Message): class Baz(proto.Message): value = proto.Field(proto.INT32, number=1) + baz = proto.Field(proto.MESSAGE, number=1, message=Baz) + bar = proto.Field(proto.MESSAGE, number=1, message=Bar) foo = Foo(bar=Foo.Bar(baz=Foo.Bar.Baz(value=42))) @@ -46,14 +49,15 @@ class Bar(proto.Message): class Baz(proto.Message): class Bacon(proto.Message): value = proto.Field(proto.INT32, number=1) + bacon = proto.Field(proto.MESSAGE, number=1, message=Bacon) + bar = proto.Field(proto.MESSAGE, number=1, message=Bar) baz = proto.Field(proto.MESSAGE, number=2, message=Baz) foo = Foo( - bar={'spam': 'xyz', 'eggs': False}, - baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), + bar={"spam": "xyz", "eggs": False}, baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), ) - assert foo.bar.spam == 'xyz' + assert foo.bar.spam == "xyz" assert not foo.bar.eggs assert foo.baz.bacon.value == 42 diff --git a/packages/proto-plus/tests/test_modules.py b/packages/proto-plus/tests/test_modules.py index 0c78eab81a13..897ce46cf075 100644 --- a/packages/proto-plus/tests/test_modules.py +++ b/packages/proto-plus/tests/test_modules.py @@ -22,14 +22,15 @@ def test_module_package(): - sys.modules[__name__].__protobuf__ = proto.module(package='spam.eggs.v1') + sys.modules[__name__].__protobuf__ = proto.module(package="spam.eggs.v1") try: + class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) - marshal = proto.Marshal(name='spam.eggs.v1') + marshal = proto.Marshal(name="spam.eggs.v1") - assert Foo.meta.package == 'spam.eggs.v1' + assert Foo.meta.package == "spam.eggs.v1" assert Foo.pb() in marshal._rules finally: del sys.modules[__name__].__protobuf__ @@ -37,32 +38,29 @@ class Foo(proto.Message): def test_module_package_explicit_marshal(): sys.modules[__name__].__protobuf__ = proto.module( - package='spam.eggs.v1', - marshal='foo', + package="spam.eggs.v1", marshal="foo", ) try: + class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) - marshal = proto.Marshal(name='foo') + marshal = proto.Marshal(name="foo") - assert Foo.meta.package == 'spam.eggs.v1' + assert Foo.meta.package == "spam.eggs.v1" assert Foo.pb() in marshal._rules finally: del sys.modules[__name__].__protobuf__ def test_module_manifest(): - __protobuf__ = proto.module( - manifest={'Foo', 'Bar', 'Baz'}, - package='spam.eggs.v1', - ) + __protobuf__ = proto.module(manifest={"Foo", "Bar", "Baz"}, package="spam.eggs.v1",) # We want to fake a module, but modules have attribute access, and # `frame.f_locals` is a dictionary. Since we only actually care about # getattr, this is reasonably easy to shim over. frame = inspect.currentframe() - with mock.patch.object(inspect, 'getmodule') as getmodule: + with mock.patch.object(inspect, "getmodule") as getmodule: getmodule.side_effect = lambda *a: View(frame.f_locals) class Foo(proto.Message): @@ -91,6 +89,7 @@ class Baz(proto.Message): class View: """A view around a mapping, for attribute-like access.""" + def __init__(self, mapping): self._mapping = mapping From a03e0ea0591e90b4f640e1a2b75ae51377895538 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 16 Jul 2020 11:53:16 -0700 Subject: [PATCH 029/272] chore: add style check and package publishing ci tasks (#43) --- packages/proto-plus/.circleci/.pypirc.enc | 3 +- packages/proto-plus/.circleci/config.yml | 49 +++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/.circleci/.pypirc.enc b/packages/proto-plus/.circleci/.pypirc.enc index 7ec2f8900289..880107333821 100644 --- a/packages/proto-plus/.circleci/.pypirc.enc +++ b/packages/proto-plus/.circleci/.pypirc.enc @@ -1 +1,2 @@ -Salted__kҝndzKp~)ćY@;eZs}@8(i09հHڽRٔ@W1 ֝4 \ No newline at end of file +Salted__yk^7l#p׾Pn.6c.;1 c1d`ĝ_ \ R +jQ85t%H e \ No newline at end of file diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index df043690bbb6..096be098ad00 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -4,6 +4,18 @@ workflows: version: 2 tests: jobs: + - style-check: + filters: + tags: + only: /^v\d+\.\d+\.\d+$/ + - publish_package: + requires: + - style-check + filters: + branches: + ignore: /.*/ + tags: + only: /^v\d+\.\d+\.\d+$/ - unit-3.5: filters: tags: @@ -175,3 +187,40 @@ jobs: - run: name: Publish to PyPI. command: python setup.py sdist upload + style-check: + docker: + - image: python:3.8-slim + steps: + - checkout + - run: + name: Install git + command: | + apt-get update + apt-get install -y git + - run: + name: Install black + command: | + pip install black + - run: + name: Format files + command: | + black . + - run: + name: Check diff + command: | + git diff --ignore-submodules=all --color --exit-code . + publish_package: + docker: + - image: python:3.8-slim + steps: + - checkout + - run: + name: Decrypt the PyPI key. + command: | + openssl aes-256-cbc -d \ + -in .circleci/.pypirc.enc \ + -out ~/.pypirc \ + -k "${PYPIRC_ENCRYPTION_KEY}" + - run: + name: Publish to PyPI. + command: python setup.py sdist upload From 12ba7d12bb994cbc58e64d85540bbcf215124f7b Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 16 Jul 2020 13:35:45 -0700 Subject: [PATCH 030/272] chore: bump version to 1.2.0 (#44) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 0618351aa6f2..fe2688af1024 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -25,7 +25,7 @@ setup( name="proto-plus", - version="1.1.0", + version="1.2.0", license="Apache 2.0", author="Dov Shlachter", author_email="dovs@google.com", From b652d410150c76db92dba1e1ce41e1ea7134463e Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Thu, 16 Jul 2020 13:54:44 -0700 Subject: [PATCH 031/272] feat: add DatetimeWithNanoseconds class to maintain Timestamp pb precision. (#40) Ports pieces of https://github.com/googleapis/python-api-core/blob/5e5559202891f7e5b6c22c2cbc549e1ec26eb857/google/api_core/datetime_helpers.py to protoplus. Particularly, a datetime implementation that provides nanoseconds. Fixes #38 --- packages/proto-plus/docs/marshal.rst | 2 + packages/proto-plus/noxfile.py | 2 +- packages/proto-plus/proto/datetime_helpers.py | 224 ++++++++++++++ .../proto-plus/proto/marshal/rules/dates.py | 15 +- .../proto-plus/tests/test_datetime_helpers.py | 290 ++++++++++++++++++ .../tests/test_fields_repeated_composite.py | 9 +- .../tests/test_marshal_types_dates.py | 24 +- 7 files changed, 545 insertions(+), 21 deletions(-) create mode 100644 packages/proto-plus/proto/datetime_helpers.py create mode 100644 packages/proto-plus/tests/test_datetime_helpers.py diff --git a/packages/proto-plus/docs/marshal.rst b/packages/proto-plus/docs/marshal.rst index c657b1839ee6..761f297c2855 100644 --- a/packages/proto-plus/docs/marshal.rst +++ b/packages/proto-plus/docs/marshal.rst @@ -37,6 +37,8 @@ Protocol buffer type Python type Nullable Protocol buffers include well-known types for ``Timestamp`` and ``Duration``, both of which have nanosecond precision. However, the Python ``datetime`` and ``timedelta`` objects have only microsecond + precision. This library converts timestamps to an implementation of + ``datetime.datetime``, DatetimeWithNanoseconds, that includes nanosecond precision. If you *write* a timestamp field using a Python ``datetime`` value, diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 8e4b6f0a4270..a1794bb91292 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -23,7 +23,7 @@ def unit(session, proto="python"): """Run the unit test suite.""" session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto - session.install("coverage", "pytest", "pytest-cov") + session.install("coverage", "pytest", "pytest-cov", "pytz") session.install("-e", ".") session.run( diff --git a/packages/proto-plus/proto/datetime_helpers.py b/packages/proto-plus/proto/datetime_helpers.py new file mode 100644 index 000000000000..66732ba5ecd7 --- /dev/null +++ b/packages/proto-plus/proto/datetime_helpers.py @@ -0,0 +1,224 @@ +# Copyright 2017 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for :mod:`datetime`.""" + +import calendar +import datetime +import re + +from google.protobuf import timestamp_pb2 + + +_UTC_EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=datetime.timezone.utc) +_RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ" +_RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S" +# datetime.strptime cannot handle nanosecond precision: parse w/ regex +_RFC3339_NANOS = re.compile( + r""" + (?P + \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} # YYYY-MM-DDTHH:MM:SS + ) + ( # Optional decimal part + \. # decimal point + (?P\d{1,9}) # nanoseconds, maybe truncated + )? + Z # Zulu +""", + re.VERBOSE, +) + + +def _from_microseconds(value): + """Convert timestamp in microseconds since the unix epoch to datetime. + + Args: + value (float): The timestamp to convert, in microseconds. + + Returns: + datetime.datetime: The datetime object equivalent to the timestamp in + UTC. + """ + return _UTC_EPOCH + datetime.timedelta(microseconds=value) + + +def _to_rfc3339(value, ignore_zone=True): + """Convert a datetime to an RFC3339 timestamp string. + + Args: + value (datetime.datetime): + The datetime object to be converted to a string. + ignore_zone (bool): If True, then the timezone (if any) of the + datetime object is ignored and the datetime is treated as UTC. + + Returns: + str: The RFC3339 formated string representing the datetime. + """ + if not ignore_zone and value.tzinfo is not None: + # Convert to UTC and remove the time zone info. + value = value.replace(tzinfo=None) - value.utcoffset() + + return value.strftime(_RFC3339_MICROS) + + +class DatetimeWithNanoseconds(datetime.datetime): + """Track nanosecond in addition to normal datetime attrs. + + Nanosecond can be passed only as a keyword argument. + """ + + __slots__ = ("_nanosecond",) + + # pylint: disable=arguments-differ + def __new__(cls, *args, **kw): + nanos = kw.pop("nanosecond", 0) + if nanos > 0: + if "microsecond" in kw: + raise TypeError("Specify only one of 'microsecond' or 'nanosecond'") + kw["microsecond"] = nanos // 1000 + inst = datetime.datetime.__new__(cls, *args, **kw) + inst._nanosecond = nanos or 0 + return inst + + # pylint: disable=arguments-differ + def replace(self, *args, **kw): + """Return a date with the same value, except for those parameters given + new values by whichever keyword arguments are specified. For example, + if d == date(2002, 12, 31), then + d.replace(day=26) == date(2002, 12, 26). + NOTE: nanosecond and microsecond are mutually exclusive arguemnts. + """ + + ms_provided = "microsecond" in kw + ns_provided = "nanosecond" in kw + provided_ns = kw.pop("nanosecond", 0) + + prev_nanos = self.nanosecond + + if ms_provided and ns_provided: + raise TypeError("Specify only one of 'microsecond' or 'nanosecond'") + + if ns_provided: + # if nanos were provided, manipulate microsecond kw arg to super + kw["microsecond"] = provided_ns // 1000 + inst = super().replace(*args, **kw) + + if ms_provided: + # ms were provided, nanos are invalid, build from ms + inst._nanosecond = inst.microsecond * 1000 + elif ns_provided: + # ns were provided, replace nanoseconds to match after calling super + inst._nanosecond = provided_ns + else: + # if neither ms or ns were provided, passthru previous nanos. + inst._nanosecond = prev_nanos + + return inst + + @property + def nanosecond(self): + """Read-only: nanosecond precision.""" + return self._nanosecond or self.microsecond * 1000 + + def rfc3339(self): + """Return an RFC3339-compliant timestamp. + + Returns: + (str): Timestamp string according to RFC3339 spec. + """ + if self._nanosecond == 0: + return _to_rfc3339(self) + nanos = str(self._nanosecond).rjust(9, "0").rstrip("0") + return "{}.{}Z".format(self.strftime(_RFC3339_NO_FRACTION), nanos) + + @classmethod + def from_rfc3339(cls, stamp): + """Parse RFC3339-compliant timestamp, preserving nanoseconds. + + Args: + stamp (str): RFC3339 stamp, with up to nanosecond precision + + Returns: + :class:`DatetimeWithNanoseconds`: + an instance matching the timestamp string + + Raises: + ValueError: if `stamp` does not match the expected format + """ + with_nanos = _RFC3339_NANOS.match(stamp) + if with_nanos is None: + raise ValueError( + "Timestamp: {}, does not match pattern: {}".format( + stamp, _RFC3339_NANOS.pattern + ) + ) + bare = datetime.datetime.strptime( + with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION + ) + fraction = with_nanos.group("nanos") + if fraction is None: + nanos = 0 + else: + scale = 9 - len(fraction) + nanos = int(fraction) * (10 ** scale) + return cls( + bare.year, + bare.month, + bare.day, + bare.hour, + bare.minute, + bare.second, + nanosecond=nanos, + tzinfo=datetime.timezone.utc, + ) + + def timestamp_pb(self): + """Return a timestamp message. + + Returns: + (:class:`~google.protobuf.timestamp_pb2.Timestamp`): Timestamp message + """ + inst = ( + self + if self.tzinfo is not None + else self.replace(tzinfo=datetime.timezone.utc) + ) + delta = inst - _UTC_EPOCH + seconds = int(delta.total_seconds()) + nanos = self._nanosecond or self.microsecond * 1000 + return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos) + + @classmethod + def from_timestamp_pb(cls, stamp): + """Parse RFC3339-compliant timestamp, preserving nanoseconds. + + Args: + stamp (:class:`~google.protobuf.timestamp_pb2.Timestamp`): timestamp message + + Returns: + :class:`DatetimeWithNanoseconds`: + an instance matching the timestamp message + """ + microseconds = int(stamp.seconds * 1e6) + bare = _from_microseconds(microseconds) + return cls( + bare.year, + bare.month, + bare.day, + bare.hour, + bare.minute, + bare.second, + nanosecond=stamp.nanos, + tzinfo=datetime.timezone.utc, + ) diff --git a/packages/proto-plus/proto/marshal/rules/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py index 37a2844d6b65..0b159830b4e7 100644 --- a/packages/proto-plus/proto/marshal/rules/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -18,6 +18,7 @@ from google.protobuf import duration_pb2 from google.protobuf import timestamp_pb2 +from proto import datetime_helpers, utils class TimestampRule: @@ -29,20 +30,18 @@ class TimestampRule: proto directly. """ - def to_python(self, value, *, absent: bool = None) -> datetime: + def to_python( + self, value, *, absent: bool = None + ) -> datetime_helpers.DatetimeWithNanoseconds: if isinstance(value, timestamp_pb2.Timestamp): if absent: return None - return datetime.fromtimestamp( - value.seconds + value.nanos / 1e9, tz=timezone.utc, - ) + return datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(value) return value def to_proto(self, value) -> timestamp_pb2.Timestamp: - if isinstance(value, datetime): - return timestamp_pb2.Timestamp( - seconds=int(value.timestamp()), nanos=value.microsecond * 1000, - ) + if isinstance(value, datetime_helpers.DatetimeWithNanoseconds): + return value.timestamp_pb() return value diff --git a/packages/proto-plus/tests/test_datetime_helpers.py b/packages/proto-plus/tests/test_datetime_helpers.py new file mode 100644 index 000000000000..84001f92ac66 --- /dev/null +++ b/packages/proto-plus/tests/test_datetime_helpers.py @@ -0,0 +1,290 @@ +# Copyright 2017, Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import calendar +import datetime + +import pytest +import pytz + +from proto import datetime_helpers +from google.protobuf import timestamp_pb2 + + +ONE_MINUTE_IN_MICROSECONDS = 60 * 1e6 + + +def test_from_microseconds(): + five_mins_from_epoch_in_microseconds = 5 * ONE_MINUTE_IN_MICROSECONDS + five_mins_from_epoch_datetime = datetime.datetime( + 1970, 1, 1, 0, 5, 0, tzinfo=datetime.timezone.utc + ) + + result = datetime_helpers._from_microseconds(five_mins_from_epoch_in_microseconds) + + assert result == five_mins_from_epoch_datetime + + +def test_to_rfc3339(): + value = datetime.datetime(2016, 4, 5, 13, 30, 0) + expected = "2016-04-05T13:30:00.000000Z" + assert datetime_helpers._to_rfc3339(value) == expected + + +def test_to_rfc3339_with_utc(): + value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=datetime.timezone.utc) + expected = "2016-04-05T13:30:00.000000Z" + assert datetime_helpers._to_rfc3339(value, ignore_zone=False) == expected + + +def test_to_rfc3339_with_non_utc(): + zone = pytz.FixedOffset(-60) + value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone) + expected = "2016-04-05T14:30:00.000000Z" + assert datetime_helpers._to_rfc3339(value, ignore_zone=False) == expected + + +def test_to_rfc3339_with_non_utc_ignore_zone(): + zone = pytz.FixedOffset(-60) + value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone) + expected = "2016-04-05T13:30:00.000000Z" + assert datetime_helpers._to_rfc3339(value, ignore_zone=True) == expected + + +def test_ctor_wo_nanos(): + stamp = datetime_helpers.DatetimeWithNanoseconds(2016, 12, 20, 21, 13, 47, 123456) + assert stamp.year == 2016 + assert stamp.month == 12 + assert stamp.day == 20 + assert stamp.hour == 21 + assert stamp.minute == 13 + assert stamp.second == 47 + assert stamp.microsecond == 123456 + assert stamp.nanosecond == 123456000 + + +def test_ctor_w_nanos(): + stamp = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, nanosecond=123456789 + ) + assert stamp.year == 2016 + assert stamp.month == 12 + assert stamp.day == 20 + assert stamp.hour == 21 + assert stamp.minute == 13 + assert stamp.second == 47 + assert stamp.microsecond == 123456 + assert stamp.nanosecond == 123456789 + + +def test_ctor_w_micros_positional_and_nanos(): + with pytest.raises(TypeError): + datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, 123456, nanosecond=123456789 + ) + + +def test_ctor_w_micros_keyword_and_nanos(): + with pytest.raises(TypeError): + datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, microsecond=123456, nanosecond=123456789 + ) + + +def test_rfc3339_wo_nanos(): + stamp = datetime_helpers.DatetimeWithNanoseconds(2016, 12, 20, 21, 13, 47, 123456) + assert stamp.rfc3339() == "2016-12-20T21:13:47.123456Z" + + +def test_rfc3339_wo_nanos_w_leading_zero(): + stamp = datetime_helpers.DatetimeWithNanoseconds(2016, 12, 20, 21, 13, 47, 1234) + assert stamp.rfc3339() == "2016-12-20T21:13:47.001234Z" + + +def test_rfc3339_w_nanos(): + stamp = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, nanosecond=123456789 + ) + assert stamp.rfc3339() == "2016-12-20T21:13:47.123456789Z" + + +def test_rfc3339_w_nanos_w_leading_zero(): + stamp = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, nanosecond=1234567 + ) + assert stamp.rfc3339() == "2016-12-20T21:13:47.001234567Z" + + +def test_rfc3339_w_nanos_no_trailing_zeroes(): + stamp = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, nanosecond=100000000 + ) + assert stamp.rfc3339() == "2016-12-20T21:13:47.1Z" + + +def test_rfc3339_w_nanos_w_leading_zero_and_no_trailing_zeros(): + stamp = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, nanosecond=1234500 + ) + assert stamp.rfc3339() == "2016-12-20T21:13:47.0012345Z" + + +def test_from_rfc3339_w_invalid(): + stamp = "2016-12-20T21:13:47" + with pytest.raises(ValueError): + datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(stamp) + + +def test_from_rfc3339_wo_fraction(): + timestamp = "2016-12-20T21:13:47Z" + expected = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, tzinfo=datetime.timezone.utc + ) + stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) + assert stamp == expected + + +def test_from_rfc3339_w_partial_precision(): + timestamp = "2016-12-20T21:13:47.1Z" + expected = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, microsecond=100000, tzinfo=datetime.timezone.utc + ) + stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) + assert stamp == expected + + +def test_from_rfc3339_w_full_precision(): + timestamp = "2016-12-20T21:13:47.123456789Z" + expected = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=datetime.timezone.utc + ) + stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) + assert stamp == expected + + +@staticmethod +@pytest.mark.parametrize( + "fractional, nanos", + [ + ("12345678", 123456780), + ("1234567", 123456700), + ("123456", 123456000), + ("12345", 123450000), + ("1234", 123400000), + ("123", 123000000), + ("12", 120000000), + ("1", 100000000), + ], +) +def test_from_rfc3339_test_nanoseconds(fractional, nanos): + value = "2009-12-17T12:44:32.{}Z".format(fractional) + assert ( + datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(value).nanosecond == nanos + ) + + +def test_timestamp_pb_wo_nanos_naive(): + stamp = datetime_helpers.DatetimeWithNanoseconds(2016, 12, 20, 21, 13, 47, 123456) + delta = stamp.replace(tzinfo=datetime.timezone.utc) - datetime_helpers._UTC_EPOCH + seconds = int(delta.total_seconds()) + nanos = 123456000 + timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos) + assert stamp.timestamp_pb() == timestamp + + +def test_timestamp_pb_w_nanos(): + stamp = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=datetime.timezone.utc + ) + delta = stamp - datetime_helpers._UTC_EPOCH + timestamp = timestamp_pb2.Timestamp( + seconds=int(delta.total_seconds()), nanos=123456789 + ) + assert stamp.timestamp_pb() == timestamp + + +def test_from_timestamp_pb_wo_nanos(): + when = datetime.datetime( + 2016, 12, 20, 21, 13, 47, 123456, tzinfo=datetime.timezone.utc + ) + delta = when - datetime_helpers._UTC_EPOCH + seconds = int(delta.total_seconds()) + timestamp = timestamp_pb2.Timestamp(seconds=seconds) + + stamp = datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(timestamp) + + assert _to_seconds(when) == _to_seconds(stamp) + assert stamp.microsecond == 0 + assert stamp.nanosecond == 0 + assert stamp.tzinfo == datetime.timezone.utc + + +def test_replace(): + stamp = datetime_helpers.DatetimeWithNanoseconds( + 2016, 12, 20, 21, 13, 47, 123456, tzinfo=datetime.timezone.utc + ) + + # ns and ms provided raises + with pytest.raises(TypeError): + stamp.replace(microsecond=1, nanosecond=0) + + # No Nanoseconds or Microseconds + new_stamp = stamp.replace(year=2015) + assert new_stamp.year == 2015 + assert new_stamp.microsecond == 123456 + assert new_stamp.nanosecond == 123456000 + + # Nanos + new_stamp = stamp.replace(nanosecond=789123) + assert new_stamp.microsecond == 789 + assert new_stamp.nanosecond == 789123 + + # Micros + new_stamp = stamp.replace(microsecond=456) + assert new_stamp.microsecond == 456 + assert new_stamp.nanosecond == 456000 + + # assert _to_seconds(when) == _to_seconds(stamp) + # assert stamp.microsecond == 0 + # assert stamp.nanosecond == 0 + # assert stamp.tzinfo == datetime.timezone.utc + + +def test_from_timestamp_pb_w_nanos(): + when = datetime.datetime( + 2016, 12, 20, 21, 13, 47, 123456, tzinfo=datetime.timezone.utc + ) + delta = when - datetime_helpers._UTC_EPOCH + seconds = int(delta.total_seconds()) + timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=123456789) + + stamp = datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(timestamp) + + assert _to_seconds(when) == _to_seconds(stamp) + assert stamp.microsecond == 123456 + assert stamp.nanosecond == 123456789 + assert stamp.tzinfo == datetime.timezone.utc + + +def _to_seconds(value): + """Convert a datetime to seconds since the unix epoch. + + Args: + value (datetime.datetime): The datetime to covert. + + Returns: + int: Microseconds since the unix epoch. + """ + assert value.tzinfo is datetime.timezone.utc + return calendar.timegm(value.timetuple()) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index 34b3933af46b..2990eea6597b 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -20,6 +20,7 @@ from google.protobuf import timestamp_pb2 import proto +from proto.datetime_helpers import DatetimeWithNanoseconds def test_repeated_composite_init(): @@ -76,10 +77,12 @@ class Foo(proto.Message): proto.MESSAGE, message=timestamp_pb2.Timestamp, number=1, ) - foo = Foo(timestamps=[datetime(2012, 4, 21, 15, tzinfo=timezone.utc)]) + foo = Foo( + timestamps=[DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)] + ) foo.timestamps.append(timestamp_pb2.Timestamp(seconds=86400 * 365)) - foo.timestamps.append(datetime(2017, 10, 14, tzinfo=timezone.utc)) - assert all([isinstance(i, datetime) for i in foo.timestamps]) + foo.timestamps.append(DatetimeWithNanoseconds(2017, 10, 14, tzinfo=timezone.utc)) + assert all([isinstance(i, DatetimeWithNanoseconds) for i in foo.timestamps]) assert all([isinstance(i, timestamp_pb2.Timestamp) for i in Foo.pb(foo).timestamps]) assert foo.timestamps[0].year == 2012 assert foo.timestamps[0].month == 4 diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 991633b59965..4e9bebabefc7 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -21,6 +21,8 @@ import proto from proto.marshal.marshal import BaseMarshal +from proto import datetime_helpers +from proto.datetime_helpers import DatetimeWithNanoseconds def test_timestamp_read(): @@ -30,7 +32,8 @@ class Foo(proto.Message): ) foo = Foo(event_time=timestamp_pb2.Timestamp(seconds=1335020400)) - assert isinstance(foo.event_time, datetime) + + assert isinstance(foo.event_time, DatetimeWithNanoseconds) assert foo.event_time.year == 2012 assert foo.event_time.month == 4 assert foo.event_time.day == 21 @@ -45,8 +48,8 @@ class Foo(proto.Message): proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) - foo = Foo(event_time=datetime(2012, 4, 21, 15, tzinfo=timezone.utc)) - assert isinstance(foo.event_time, datetime) + foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) + assert isinstance(foo.event_time, DatetimeWithNanoseconds) assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) assert foo.event_time.year == 2012 assert foo.event_time.month == 4 @@ -61,8 +64,9 @@ class Foo(proto.Message): ) foo = Foo() - foo.event_time = datetime(2012, 4, 21, 15, tzinfo=timezone.utc) - assert isinstance(foo.event_time, datetime) + dns = DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc) + foo.event_time = dns + assert isinstance(foo.event_time, DatetimeWithNanoseconds) assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) assert foo.event_time.year == 2012 assert foo.event_time.month == 4 @@ -78,7 +82,7 @@ class Foo(proto.Message): foo = Foo() foo.event_time = timestamp_pb2.Timestamp(seconds=1335020400) - assert isinstance(foo.event_time, datetime) + assert isinstance(foo.event_time, DatetimeWithNanoseconds) assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) assert foo.event_time.year == 2012 assert foo.event_time.month == 4 @@ -93,7 +97,9 @@ class Foo(proto.Message): ) foo = Foo() - foo.event_time = datetime(2012, 4, 21, 15, 0, 0, 1, tzinfo=timezone.utc) + foo.event_time = DatetimeWithNanoseconds( + 2012, 4, 21, 15, 0, 0, 1, tzinfo=timezone.utc + ) assert foo.event_time.microsecond == 1 assert Foo.pb(foo).event_time.nanos == 1000 foo.event_time = foo.event_time.replace(microsecond=2) @@ -117,7 +123,7 @@ class Foo(proto.Message): proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) - foo = Foo(event_time=datetime(2012, 4, 21, 15, tzinfo=timezone.utc)) + foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) del foo.event_time assert foo.event_time is None @@ -202,7 +208,7 @@ def test_timestamp_to_python_idempotent(): # However, we test idempotency for consistency with `to_proto` and # general resiliency. marshal = BaseMarshal() - py_value = datetime(2012, 4, 21, 15, tzinfo=timezone.utc) + py_value = DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc) assert marshal.to_python(timestamp_pb2.Timestamp, py_value) is py_value From a5459834caef624a4640d4555fd59a3b20c65d57 Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Thu, 16 Jul 2020 14:28:50 -0700 Subject: [PATCH 032/272] fix: Modify setup.py to indicate this is google maintained (#45) * fix: Modify setup.py to indicate this is google maintained * fix: add 3.8 trove --- packages/proto-plus/setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index fe2688af1024..1103494b1dad 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -27,8 +27,8 @@ name="proto-plus", version="1.2.0", license="Apache 2.0", - author="Dov Shlachter", - author_email="dovs@google.com", + author="Google LLC", + author_email="googleapis-packages@google.com", url="https://github.com/googleapis/proto-plus-python.git", packages=find_packages(exclude=["docs", "tests"]), description="Beautiful, Pythonic protocol buffers.", @@ -45,6 +45,7 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", ], From 4dcb59002d73e116e0c50f1e29f11926aba8a181 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 16 Jul 2020 14:45:07 -0700 Subject: [PATCH 033/272] chore: add a release-please config (#46) --- packages/proto-plus/.github/release-please.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/proto-plus/.github/release-please.yml diff --git a/packages/proto-plus/.github/release-please.yml b/packages/proto-plus/.github/release-please.yml new file mode 100644 index 000000000000..4507ad0598a5 --- /dev/null +++ b/packages/proto-plus/.github/release-please.yml @@ -0,0 +1 @@ +releaseType: python From faaa1e4843c29476ff2db77557a4fb7a62944af6 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 16 Jul 2020 14:45:52 -0700 Subject: [PATCH 034/272] chore: add release please (#47) --- packages/proto-plus/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 1103494b1dad..53570fdf1ffd 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,6 +17,7 @@ from setuptools import find_packages, setup +version = "1.2.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) @@ -25,7 +26,7 @@ setup( name="proto-plus", - version="1.2.0", + version=version, license="Apache 2.0", author="Google LLC", author_email="googleapis-packages@google.com", From 2fdcb420b1373e971f742144c0166945a72428f3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2020 15:02:10 -0700 Subject: [PATCH 035/272] chore: release 1.3.0 (#48) * created CHANGELOG.md [ci skip] * updated setup.py * Update CHANGELOG.md Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Dov Shlachter --- packages/proto-plus/CHANGELOG.md | 15 +++++++++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 packages/proto-plus/CHANGELOG.md diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md new file mode 100644 index 000000000000..e4d8507568c2 --- /dev/null +++ b/packages/proto-plus/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +## [1.3.0](https://www.github.com/googleapis/proto-plus-python/compare/1.2.0...v1.3.0) (2020-07-16) + + +### Features + +* add convenience methods to convert to/from json ([#39](https://www.github.com/googleapis/proto-plus-python/issues/39)) ([2868946](https://www.github.com/googleapis/proto-plus-python/commit/286894609843f568c9ff367ab79542783642b801)) +* add DatetimeWithNanoseconds class to maintain Timestamp pb precision. ([#40](https://www.github.com/googleapis/proto-plus-python/issues/40)) ([a17ccd5](https://www.github.com/googleapis/proto-plus-python/commit/a17ccd52c7fa3609ce79fde84b931c0693f53171)), closes [#38](https://www.github.com/googleapis/proto-plus-python/issues/38) +* add support for proto3 optional fields ([#35](https://www.github.com/googleapis/proto-plus-python/issues/35)) ([0eb5762](https://www.github.com/googleapis/proto-plus-python/commit/0eb5762681e315635db1dffd583d91a4f32cba43)) + + +### Bug Fixes + +* Modify setup.py to indicate this is google maintained ([#45](https://www.github.com/googleapis/proto-plus-python/issues/45)) ([96b3b00](https://www.github.com/googleapis/proto-plus-python/commit/96b3b00dd6712fe44e71dedf8080b20544e95416)) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 53570fdf1ffd..d33667f2427b 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.2.0" +version = "1.3.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From a18b9a2588530e3d897829c4e4162e42731c905f Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 16 Jul 2020 17:16:48 -0700 Subject: [PATCH 036/272] docs: linkify pypi badge (#50) --- packages/proto-plus/README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 7b634511d469..140248c05cb8 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -21,6 +21,7 @@ Documentation .. _documentation: https://proto-plus-python.readthedocs.io/en/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/proto-plus.svg + :target: https://pypi.org/project/proto-plus .. |release level| image:: https://img.shields.io/badge/release%20level-ga-gold.svg?style=flat :target: https://cloud.google.com/terms/launch-stages .. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest From 74e38475d2ed165f59a9e782feebf30847a54e6b Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 21 Jul 2020 11:32:37 -0700 Subject: [PATCH 037/272] fix: tweak pypi circleci task (#54) --- packages/proto-plus/.circleci/.pypirc.enc | Bin 96 -> 31 bytes packages/proto-plus/.circleci/config.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.circleci/.pypirc.enc b/packages/proto-plus/.circleci/.pypirc.enc index 880107333821e0ae9564b3974fed861cc071f458..b2c90332ed375210bb38dd4554eba297a2c22d1a 100644 GIT binary patch literal 31 ncmWGe%qdAtiH{Ep&vU)9TdC5R!PjQ_vKeYGyElDrf4&v~%(M=r literal 96 zcmV-m0H6O;VQh3|WM5x-Ys_B7H*6zt Date: Tue, 21 Jul 2020 16:05:59 -0700 Subject: [PATCH 038/272] chore: release 1.3.1 (#51) * updated CHANGELOG.md [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 12 ++++++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index e4d8507568c2..aabc25fcbaa1 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +### [1.3.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.0...v1.3.1) (2020-07-21) + + +### Bug Fixes + +* tweak pypi circleci task ([#54](https://www.github.com/googleapis/proto-plus-python/issues/54)) ([89c49d7](https://www.github.com/googleapis/proto-plus-python/commit/89c49d700d4b6e9a434fbfced8ca39d430dd22f9)) + + +### Documentation + +* linkify pypi badge ([#50](https://www.github.com/googleapis/proto-plus-python/issues/50)) ([8ff08e2](https://www.github.com/googleapis/proto-plus-python/commit/8ff08e21e75570aad71c5e62f4c78b43139b5df2)) + ## [1.3.0](https://www.github.com/googleapis/proto-plus-python/compare/1.2.0...v1.3.0) (2020-07-16) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index d33667f2427b..406ab8fe1429 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.3.0" +version = "1.3.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 824be7e67f21a88796a0feb316fdff1bacb01458 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 21 Jul 2020 16:15:29 -0700 Subject: [PATCH 039/272] fix: correctly handle passed in vanilla datetime.datetime (#57) Fixes googleapis/gapic-generator-python#544 --- packages/proto-plus/proto/marshal/rules/dates.py | 4 ++++ .../proto-plus/tests/test_marshal_types_dates.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/proto-plus/proto/marshal/rules/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py index 0b159830b4e7..1e0695ec2f9e 100644 --- a/packages/proto-plus/proto/marshal/rules/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -42,6 +42,10 @@ def to_python( def to_proto(self, value) -> timestamp_pb2.Timestamp: if isinstance(value, datetime_helpers.DatetimeWithNanoseconds): return value.timestamp_pb() + if isinstance(value, datetime): + return timestamp_pb2.Timestamp( + seconds=int(value.timestamp()), nanos=value.microsecond * 1000, + ) return value diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 4e9bebabefc7..631858935226 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -221,3 +221,15 @@ def test_duration_to_python_idempotent(): marshal = BaseMarshal() py_value = timedelta(seconds=240) assert marshal.to_python(duration_pb2.Duration, py_value) is py_value + + +def test_vanilla_datetime_construction(): + # 99% of users are going to want to pass in regular datetime objects. + # Make sure that this interoperates well with nanosecond precision. + class User(proto.Message): + birthday = proto.Field(timestamp_pb2.Timestamp, number=1) + + # Our user WAs born yesterday. + bday = datetime.now(tz=timezone.utc) + timedelta(days=-1) + u = User(birthday=bday) + assert u.birthday == bday From bc297e54bf08581ecf2d40f2008d0b2da0ef08d2 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 21 Jul 2020 17:38:06 -0700 Subject: [PATCH 040/272] fix: update encrypted pypi passwd (#58) --- packages/proto-plus/.circleci/.pypirc.enc | Bin 31 -> 31 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/proto-plus/.circleci/.pypirc.enc b/packages/proto-plus/.circleci/.pypirc.enc index b2c90332ed375210bb38dd4554eba297a2c22d1a..ab3625fd60805b41495d4616e01ee23933a5b3ac 100644 GIT binary patch literal 31 ncmWGe%qdAtiH{H4U$Nkv#iN_{RmIMGa*ahTz4 Date: Tue, 21 Jul 2020 17:43:18 -0700 Subject: [PATCH 041/272] chore: release 1.3.2 (#59) * updated CHANGELOG.md [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 8 ++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index aabc25fcbaa1..f130907f0da0 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### [1.3.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.1...v1.3.2) (2020-07-22) + + +### Bug Fixes + +* correctly handle passed in vanilla datetime.datetime ([#57](https://www.github.com/googleapis/proto-plus-python/issues/57)) ([a770816](https://www.github.com/googleapis/proto-plus-python/commit/a770816197cbce60ee023bd5b6ee6bd2d970ded8)), closes [googleapis/gapic-generator-python#544](https://www.github.com/googleapis/gapic-generator-python/issues/544) +* update encrypted pypi passwd ([#58](https://www.github.com/googleapis/proto-plus-python/issues/58)) ([d985233](https://www.github.com/googleapis/proto-plus-python/commit/d9852336d83717cb9ff24b6bec3ef1463239fea1)) + ### [1.3.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.0...v1.3.1) (2020-07-21) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 406ab8fe1429..ceebd668b182 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.3.1" +version = "1.3.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 5a66fbf8e2db7bc4946d132c1d00a89a72f82945 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 22 Jul 2020 17:51:07 -0700 Subject: [PATCH 042/272] feat: prevent unnecessary copies when deserializing proto (#63) * feat: prevent unnecessary copies when deserializing proto * Update message.py * Tweak json deserialization --- packages/proto-plus/proto/message.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index b4ba1d388346..3996b9d4299b 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -316,7 +316,11 @@ def deserialize(cls, payload: bytes) -> "Message": ~.Message: An instance of the message class against which this method was called. """ - return cls(cls.pb().FromString(payload)) + # Usually we don't wrap the original proto and are force to make a copy + # to prevent modifying user data. + # In this case it's perfectly reasonable to wrap the proto becasue it's + # never user visible, and it gives a huge performance boost. + return cls(cls.pb().FromString(payload), __wrap_original=True) def to_json(cls, instance) -> str: """Given a message instance, serialize it to json @@ -341,7 +345,9 @@ def from_json(cls, payload) -> "Message": ~.Message: An instance of the message class against which this method was called. """ - return cls(Parse(payload, cls()._pb)) + instance = cls() + Parse(payload, instance._pb) + return instance class Message(metaclass=MessageMeta): @@ -398,19 +404,19 @@ def __init__(self, mapping=None, **kwargs): mapping = {} mapping.update(kwargs) + # Avoid copying the mapping unnecessarily + params = {} # Update the mapping to address any values that need to be # coerced. marshal = self._meta.marshal - for key, value in copy.copy(mapping).items(): + for key, value in mapping.items(): pb_type = self._meta.fields[key].pb_type pb_value = marshal.to_proto(pb_type, value) - if pb_value is None: - mapping.pop(key) - else: - mapping[key] = pb_value + if pb_value is not None: + params[key] = pb_value # Create the internal protocol buffer. - self._pb = self._meta.pb(**mapping) + self._pb = self._meta.pb(**params) def __bool__(self): """Return True if any field is truthy, False otherwise.""" From 9efe1abe9ca02894f705b425896f61e51cc802b3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2020 18:03:41 -0700 Subject: [PATCH 043/272] chore: release 1.4.0 (#64) * updated CHANGELOG.md [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index f130907f0da0..172eadc10649 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.4.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.2...v1.4.0) (2020-07-23) + + +### Features + +* prevent unnecessary copies when deserializing proto ([#63](https://www.github.com/googleapis/proto-plus-python/issues/63)) ([5e1c061](https://www.github.com/googleapis/proto-plus-python/commit/5e1c0619b5f4c5d2a6a75ae6d45a53fef2e58823)) + ### [1.3.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.1...v1.3.2) (2020-07-22) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index ceebd668b182..b472e3c48cec 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.3.2" +version = "1.4.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 2982ab57ebe0d08d4eca613918a3b74833b69e11 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 22 Jul 2020 18:07:51 -0700 Subject: [PATCH 044/272] fix: tweak publish ci task (#65) --- packages/proto-plus/.circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index f2d57ef7ec1d..871c3e9d5238 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -217,7 +217,7 @@ jobs: - run: name: Decrypt the PyPI key. command: | - openssl aes-256-cbc -d \ + openssl aes-256-ctr -pbkdf2 -d \ -in .circleci/.pypirc.enc \ -out ~/.pypirc \ -k "${PYPIRC_ENCRYPTION_KEY}" From 42ab94882572e431b09db3a22470c2d5145b9c6c Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 23 Jul 2020 14:01:07 -0700 Subject: [PATCH 045/272] doc: add JSON interop and optional field docs (#69) Add documentation for JSON serialization/deserialization Add documentation for making optional fields and determining field presence. --- packages/proto-plus/docs/fields.rst | 48 +++++++++++++++++++++++++++ packages/proto-plus/docs/messages.rst | 18 ++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/docs/fields.rst b/packages/proto-plus/docs/fields.rst index 526d2973971e..b4aa24259f70 100644 --- a/packages/proto-plus/docs/fields.rst +++ b/packages/proto-plus/docs/fields.rst @@ -120,3 +120,51 @@ a string (which should match for all fields within the oneof): implementation of protocol buffers will reject the message. They need not have consecutive field numbers, but they must be declared in consecutive order. + + +Optional fields +--------------- + +All fields in protocol buffers are optional, but it is often necessary to +check for field presence. Sometimes legitimate values for fields can be falsy, +so checking for truthiness is not sufficient. Proto3 v3.12.0 added the +``optional`` keyword to field descriptions, which enables a mechanism for +checking field presence. + +In proto plus, fields can be marked as optional by passing ``optional=True`` +in the constructor. The message *class* then gains a field of the same name +that can be used to detect whether the field is present in message *instances*. + +.. code-block:: python + + class Song(proto.Message): + composer = proto.Field(Composer, number=1) + title = proto.Field(proto.STRING, number=2) + lyrics = proto.Field(proto.STRING, number=3) + year = proto.Field(proto.INT32, number=4) + performer = proto.Field(proto.STRING, number=5, optional=True) + + >>> s = Song( + ... composer={'given_name': 'Johann', 'family_name': 'Pachelbel'}, + ... title='Canon in D', + ... year=1680, + ... genre=Genre.CLASSICAL, + ... ) + >>> Song.performer in s + False + >>> s.performer = 'Brahms' + >>> Song.performer in s + True + >>> del s.performer + >>> Song.performer in s + False + >>> s.performer = "" # The mysterious, unnamed composer + >>> Song.performer in s + True + + +Under the hood, fields marked as optional are implemented via a synthetic +one-variant ``oneof``. See the protocolbuffers documentation_ for more +information. + +.. _documentation: https://github.com/protocolbuffers/protobuf/blob/v3.12.0/docs/field_presence.md diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index d7a58ccf0d6e..18dd4db60bef 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -50,9 +50,11 @@ A few things to note: * This library only handles proto3. * The ``number`` is really a field ID. It is *not* a value of any kind. -* All fields are optional (as is always the case in proto3). As a general - rule, there is no distinction between setting the type's falsy value and - not setting it at all (although there are exceptions to this in some cases). +* All fields are optional (as is always the case in proto3). + The only general way to determine whether a field was explicitly set to its + falsy value or not set all is to mark it ``optional``. +* Because all fields are optional, it is the responsibility of application logic + to determine whether a necessary field has been set. .. _messages: https://developers.google.com/protocol-buffers/docs/proto3#simple @@ -153,3 +155,13 @@ returns an instance of the message: .. code-block:: python song = Song.deserialize(serialized_song) + +JSON serialization and deserialization are also available from message *classes* +via the :meth:`~.Message.to_json` and :meth:`~.Message.from_json` methods. + +.. code-block:: python + + json = Song.to_json(song) + + new_song = Song.from_json(json) + From a86cd3ac913cb16f60c778ea59f5152ea2419b65 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2020 14:09:56 -0700 Subject: [PATCH 046/272] chore: release 1.4.1 (#71) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 172eadc10649..7bc58c52ff0f 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.4.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.0...v1.4.1) (2020-07-23) + + +### Bug Fixes + +* tweak publish ci task ([#65](https://www.github.com/googleapis/proto-plus-python/issues/65)) ([983189c](https://www.github.com/googleapis/proto-plus-python/commit/983189c5effa25fb9365eb63caddb425d3cfb2b5)) + ## [1.4.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.2...v1.4.0) (2020-07-23) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index b472e3c48cec..768b40e01a48 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.4.0" +version = "1.4.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 1070d7b1c5a28e32108bd468ba98b774c41a5f8a Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 23 Jul 2020 14:40:35 -0700 Subject: [PATCH 047/272] fix: getattr on an invalid field raises AttributeError (#73) Fix for #31 --- packages/proto-plus/proto/message.py | 11 +++++++---- packages/proto-plus/tests/test_message.py | 9 +++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 3996b9d4299b..b750b47fc182 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -504,10 +504,13 @@ def __getattr__(self, key): their Python equivalents. See the ``marshal`` module for more details. """ - pb_type = self._meta.fields[key].pb_type - pb_value = getattr(self._pb, key) - marshal = self._meta.marshal - return marshal.to_python(pb_type, pb_value, absent=key not in self) + try: + pb_type = self._meta.fields[key].pb_type + pb_value = getattr(self._pb, key) + marshal = self._meta.marshal + return marshal.to_python(pb_type, pb_value, absent=key not in self) + except KeyError as ex: + raise AttributeError(str(ex)) def __ne__(self, other): """Return True if the messages are unequal, False otherwise.""" diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index de0fd881d630..c79dc60b5ad1 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -207,3 +207,12 @@ class Foo(proto.Message): assert isinstance(Foo.pb(Foo()), Foo.pb()) with pytest.raises(TypeError): Foo.pb(object()) + + +def test_invalid_field_access(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + s = Squid() + with pytest.raises(AttributeError): + getattr(s, "shell") From ff06e098e9a7946738d9dfc44206ead625ca114a Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2020 14:46:59 -0700 Subject: [PATCH 048/272] chore: release 1.4.2 (#74) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 7bc58c52ff0f..01abba810cf9 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.4.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.1...v1.4.2) (2020-07-23) + + +### Bug Fixes + +* getattr on an invalid field raises AttributeError ([#73](https://www.github.com/googleapis/proto-plus-python/issues/73)) ([74ea8f0](https://www.github.com/googleapis/proto-plus-python/commit/74ea8f0cd9083939e53d1de2450b649500281b9a)), closes [#31](https://www.github.com/googleapis/proto-plus-python/issues/31) + ### [1.4.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.0...v1.4.1) (2020-07-23) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 768b40e01a48..0969ebebf939 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.4.1" +version = "1.4.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From aacd9ecb907ccc8878968b7832b8c5146d546cb1 Mon Sep 17 00:00:00 2001 From: Jon Court Date: Thu, 30 Jul 2020 11:45:51 +1200 Subject: [PATCH 049/272] feat: support fixed filename salt to allow proto-plus use with schema registry tools (#61) Salt is classname in lowercase when not declared in manifest. If manifest declared but class not in there a warning is issued to the log. Co-authored-by: Jon Court Co-authored-by: Dov Shlachter --- packages/proto-plus/.circleci/config.yml | 44 ++++++++++ packages/proto-plus/noxfile.py | 4 +- packages/proto-plus/proto/_file_info.py | 49 +++++++++-- packages/proto-plus/proto/message.py | 2 +- .../tests/test_file_info_salting.py | 77 ++++++++++++++++ .../test_file_info_salting_with_manifest.py | 88 +++++++++++++++++++ .../proto-plus/tests/test_message_filename.py | 23 +++++ ...sage_filename_with_and_without_manifest.py | 50 +++++++++++ .../test_message_filename_with_manifest.py | 37 ++++++++ 9 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 packages/proto-plus/tests/test_file_info_salting.py create mode 100644 packages/proto-plus/tests/test_file_info_salting_with_manifest.py create mode 100644 packages/proto-plus/tests/test_message_filename.py create mode 100644 packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py create mode 100644 packages/proto-plus/tests/test_message_filename_with_manifest.py diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index 871c3e9d5238..dd3631b3aa5b 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -40,6 +40,14 @@ workflows: filters: tags: only: /.*/ + - unit-3.8: + filters: + tags: + only: /.*/ + - unit-cpp-3.8: + filters: + tags: + only: /.*/ - docs: filters: tags: @@ -52,6 +60,8 @@ workflows: - unit-cpp-3.6 - unit-3.7 - unit-cpp-3.7 + - unit-3.8 + - unit-cpp-3.8 - docs filters: branches: @@ -110,6 +120,23 @@ jobs: name: Submit coverage data to codecov. command: codecov when: always + unit-3.8: + docker: + - image: python:3.8 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install nox + pip install codecov + - run: + name: Run unit tests. + command: nox -s unit-3.8 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always unit-cpp-3.5: docker: - image: python:3.5 @@ -161,6 +188,23 @@ jobs: name: Submit coverage data to codecov. command: codecov when: always + unit-cpp-3.8: + docker: + - image: python:3.8 + steps: + - checkout + - run: + name: Install nox and codecov. + command: | + pip install nox + pip install codecov + - run: + name: Run unit tests. + command: nox -s unitcpp-3.8 + - run: + name: Submit coverage data to codecov. + command: codecov + when: always docs: docker: - image: python:3.6 diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index a1794bb91292..0e7078355c8a 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -18,7 +18,7 @@ import nox -@nox.session(python=["3.5", "3.6", "3.7"]) +@nox.session(python=["3.5", "3.6", "3.7", "3.8"]) def unit(session, proto="python"): """Run the unit test suite.""" @@ -38,7 +38,7 @@ def unit(session, proto="python"): ) -@nox.session(python=["3.5", "3.6", "3.7"]) +@nox.session(python=["3.5", "3.6", "3.7", "3.8"]) def unitcpp(session): return unit(session, proto="cpp") diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 9fd5fc7d505e..2d393ce27536 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -12,10 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import collections import collections.abc import inspect -import uuid +import logging from google.protobuf import descriptor_pool from google.protobuf import message @@ -23,6 +22,8 @@ from proto.marshal.rules.message import MessageRule +log = logging.getLogger("_FileInfo") + class _FileInfo( collections.namedtuple( @@ -31,7 +32,40 @@ class _FileInfo( ): registry = {} # Mapping[str, '_FileInfo'] - def generate_file_pb(self): + def _get_manifest(self, new_class): + module = inspect.getmodule(new_class) + if hasattr(module, "__protobuf__"): + return frozenset(module.__protobuf__.manifest) + + return frozenset() + + def _get_remaining_manifest(self, new_class): + return self._get_manifest(new_class) - {new_class.__name__} + + def _has_manifest(self, new_class): + return len(self._get_manifest(new_class)) > 0 + + def _is_in_manifest(self, new_class): + return new_class.__name__ in self._get_manifest(new_class) + + def _calculate_salt(self, new_class, fallback): + if self._has_manifest(new_class=new_class) and not self._is_in_manifest( + new_class=new_class + ): + log.warning( + "proto-plus module {module} has a declared manifest but {classname} is not in it".format( + module=inspect.getmodule(new_class).__name__, + classname=new_class.__name__, + ) + ) + + return ( + "" + if self._is_in_manifest(new_class=new_class) + else (fallback or "").lower() + ) + + def generate_file_pb(self, new_class, fallback_salt=""): """Generate the descriptors for all protos in the file. This method takes the file descriptor attached to the parent @@ -47,8 +81,9 @@ def generate_file_pb(self): # Salt the filename in the descriptor. # This allows re-use of the filename by other proto messages if # needed (e.g. if __all__ is not used). - self.descriptor.name = "{prefix}_{salt}.proto".format( - prefix=self.descriptor.name[:-6], salt=str(uuid.uuid4())[0:8], + salt = self._calculate_salt(new_class, fallback_salt) + self.descriptor.name = "{name}.proto".format( + name="_".join([self.descriptor.name[:-6], salt]).rstrip("_"), ) # Add the file descriptor. @@ -122,9 +157,7 @@ def ready(self, new_class): # Do not generate the file descriptor until every member of the # manifest has been populated. module = inspect.getmodule(new_class) - manifest = frozenset() - if hasattr(module, "__protobuf__"): - manifest = module.__protobuf__.manifest.difference({new_class.__name__},) + manifest = self._get_remaining_manifest(new_class) if not all([hasattr(module, i) for i in manifest]): return False diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index b750b47fc182..c853042cde5d 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -254,7 +254,7 @@ def __new__(mcls, name, bases, attrs): # Generate the descriptor for the file if it is ready. if file_info.ready(new_class=cls): - file_info.generate_file_pb() + file_info.generate_file_pb(new_class=cls, fallback_salt=full_name) # Done; return the class. return cls diff --git a/packages/proto-plus/tests/test_file_info_salting.py b/packages/proto-plus/tests/test_file_info_salting.py new file mode 100644 index 000000000000..e87731cfbe39 --- /dev/null +++ b/packages/proto-plus/tests/test_file_info_salting.py @@ -0,0 +1,77 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import collections + +import proto +from google.protobuf import descriptor_pb2 + +from proto import _file_info, _package_info + + +def sample_file_info(name): + filename = name + ".proto" + + # Get the essential information about the proto package, and where + # this component belongs within the file. + package, marshal = _package_info.compile(name, {}) + + # Get or create the information about the file, including the + # descriptor to which the new message descriptor shall be added. + return _file_info._FileInfo.registry.setdefault( + filename, + _file_info._FileInfo( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, package=package, syntax="proto3", + ), + enums=collections.OrderedDict(), + messages=collections.OrderedDict(), + name=filename, + nested={}, + ), + ) + + +def test_fallback_salt_is_appended_to_filename(): + # given + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + name = "my-fileinfo" + fallback_salt = "my-fallback_salt" + file_info = sample_file_info(name) + + # when + file_info.generate_file_pb(new_class=Foo, fallback_salt=fallback_salt) + + # then + assert file_info.descriptor.name == name + "_" + fallback_salt + ".proto" + + +def test_none_fallback_salt_is_appended_to_filename_as_empty(): + # given + + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + name = "my-fileinfo" + none_fallback_salt = None + file_info = sample_file_info(name) + + # when + file_info.generate_file_pb(new_class=Foo, fallback_salt=none_fallback_salt) + + # then + assert file_info.descriptor.name == name + ".proto" diff --git a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py new file mode 100644 index 000000000000..0a0e7c579e99 --- /dev/null +++ b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py @@ -0,0 +1,88 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import collections + +import proto +from google.protobuf import descriptor_pb2 + +from proto import _file_info, _package_info + +PACKAGE = "a.test.package.salting.with.manifest" +__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) + + +class This(proto.Message): + this = proto.Field(proto.INT32, number=1) + + +class That(proto.Message): + that = proto.Field(proto.INT32, number=1) + + +class NotInManifest(proto.Message): + them = proto.Field(proto.INT32, number=1) + + +def sample_file_info(name): + filename = name + ".proto" + + # Get the essential information about the proto package, and where + # this component belongs within the file. + package, marshal = _package_info.compile(name, {}) + + # Get or create the information about the file, including the + # descriptor to which the new message descriptor shall be added. + return _file_info._FileInfo.registry.setdefault( + filename, + _file_info._FileInfo( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, package=package, syntax="proto3", + ), + enums=collections.OrderedDict(), + messages=collections.OrderedDict(), + name=filename, + nested={}, + ), + ) + + +def test_no_salt_is_appended_to_filename_with_manifest(): + # given + name = "my-filename" + fallback_salt = "my-fallback_salt" + file_info = sample_file_info(name) + + # when + file_info.generate_file_pb(new_class=This, fallback_salt=fallback_salt) + + # then + assert file_info.descriptor.name == name + ".proto" + + +def test_none_fallback_salt_is_appended_to_filename_as_empty(): + # given + + name = "my-fileinfo" + none_fallback_salt = None + file_info = sample_file_info(name) + + # when + file_info.generate_file_pb( + new_class=NotInManifest, fallback_salt=none_fallback_salt + ) + + # then + assert file_info.descriptor.name == name + ".proto" diff --git a/packages/proto-plus/tests/test_message_filename.py b/packages/proto-plus/tests/test_message_filename.py new file mode 100644 index 000000000000..3cce260a1206 --- /dev/null +++ b/packages/proto-plus/tests/test_message_filename.py @@ -0,0 +1,23 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import proto + + +def test_filename_includes_classname_salt(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + assert Foo.pb(Foo()).DESCRIPTOR.file.name == "test_message_filename_foo.proto" diff --git a/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py new file mode 100644 index 000000000000..32c2bc15ecaf --- /dev/null +++ b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py @@ -0,0 +1,50 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +PACKAGE = "a.test.package.with.and.without.manifest" +__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) + + +class This(proto.Message): + this = proto.Field(proto.INT32, number=1) + + +class That(proto.Message): + that = proto.Field(proto.INT32, number=1) + + +class NotInManifest(proto.Message): + them = proto.Field(proto.INT32, number=1) + + +def test_manifest_causes_exclusion_of_classname_salt(): + + assert ( + This.pb(This()).DESCRIPTOR.file.name + == "test_message_filename_with_and_without_manifest.proto" + ) + assert ( + That.pb(That()).DESCRIPTOR.file.name + == "test_message_filename_with_and_without_manifest.proto" + ) + + assert ( + NotInManifest.pb(NotInManifest()).DESCRIPTOR.file.name + == "test_message_filename_with_and_without_manifest_" + + PACKAGE + + ".notinmanifest.proto" + ) diff --git a/packages/proto-plus/tests/test_message_filename_with_manifest.py b/packages/proto-plus/tests/test_message_filename_with_manifest.py new file mode 100644 index 000000000000..e12e5a511b6f --- /dev/null +++ b/packages/proto-plus/tests/test_message_filename_with_manifest.py @@ -0,0 +1,37 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + +PACKAGE = "a.test.package.with.manifest" +__protobuf__ = proto.module(package=PACKAGE, manifest={"ThisFoo", "ThisBar"},) + + +class ThisFoo(proto.Message): + foo = proto.Field(proto.INT32, number=1) + + +class ThisBar(proto.Message): + bar = proto.Field(proto.INT32, number=2) + + +def test_manifest_causes_exclusion_of_classname_salt(): + assert ( + ThisFoo.pb(ThisFoo()).DESCRIPTOR.file.name + == "test_message_filename_with_manifest.proto" + ) + assert ( + ThisBar.pb(ThisBar()).DESCRIPTOR.file.name + == "test_message_filename_with_manifest.proto" + ) From 0f01ac8d8d8f2fe3a9f90324aacfc4415c542851 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2020 16:49:13 -0700 Subject: [PATCH 050/272] chore: release 1.5.0 (#82) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 01abba810cf9..5064aac980b7 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.5.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.2...v1.5.0) (2020-07-29) + + +### Features + +* support fixed filename salt to allow proto-plus use with schema registry tools ([#61](https://www.github.com/googleapis/proto-plus-python/issues/61)) ([ea86eb9](https://www.github.com/googleapis/proto-plus-python/commit/ea86eb9ac694ed1f0e711698429449f41ecfedfc)) + ### [1.4.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.1...v1.4.2) (2020-07-23) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 0969ebebf939..9737536c156b 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.4.2" +version = "1.5.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From ad7e8c850e9d041664f6f0b22571ca43e6534318 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 29 Jul 2020 17:30:50 -0700 Subject: [PATCH 051/272] chore: use twine for publish (#83) --- packages/proto-plus/.circleci/.pypirc.enc | 1 - packages/proto-plus/.circleci/config.yml | 25 ++++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 packages/proto-plus/.circleci/.pypirc.enc diff --git a/packages/proto-plus/.circleci/.pypirc.enc b/packages/proto-plus/.circleci/.pypirc.enc deleted file mode 100644 index ab3625fd6080..000000000000 --- a/packages/proto-plus/.circleci/.pypirc.enc +++ /dev/null @@ -1 +0,0 @@ -Salted__Vx8?zsCm39Ky2 \ No newline at end of file diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index dd3631b3aa5b..e3c4aeb7f71d 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -222,15 +222,16 @@ jobs: steps: - checkout - run: - name: Decrypt the PyPI key. + name: Install twine command: | - openssl aes-256-ctr -pbkdf2 -d \ - -in .circleci/.pypirc.enc \ - -out ~/.pypirc \ - -k "${PYPIRC_ENCRYPTION_KEY}" + pip install twine + - run: + name: Build the package. + command: python setup.py sdist - run: name: Publish to PyPI. - command: python setup.py sdist upload + command: twine upload dist/* + style-check: docker: - image: python:3.8-slim @@ -259,12 +260,12 @@ jobs: steps: - checkout - run: - name: Decrypt the PyPI key. + name: Install twine command: | - openssl aes-256-ctr -pbkdf2 -d \ - -in .circleci/.pypirc.enc \ - -out ~/.pypirc \ - -k "${PYPIRC_ENCRYPTION_KEY}" + pip install twine + - run: + name: Build the package. + command: python setup.py sdist - run: name: Publish to PyPI. - command: python setup.py sdist upload + command: twine upload dist/* From 642485e04c5fb22dd5a6e6644a7494ad88f59326 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 30 Jul 2020 16:06:26 -0700 Subject: [PATCH 052/272] fix: numerous small performance tweaks (#85) --- packages/proto-plus/proto/_file_info.py | 6 ++--- packages/proto-plus/proto/fields.py | 26 +++++++++----------- packages/proto-plus/proto/marshal/marshal.py | 15 +++++------ packages/proto-plus/proto/message.py | 14 +++++++---- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 2d393ce27536..530a3678f56b 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -158,11 +158,9 @@ def ready(self, new_class): # manifest has been populated. module = inspect.getmodule(new_class) manifest = self._get_remaining_manifest(new_class) - if not all([hasattr(module, i) for i in manifest]): - return False - # Okay, we are ready. - return True + # We are ready if all members have been populated. + return all(hasattr(module, i) for i in manifest) @property def unresolved_fields(self): diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index c8724673af13..c951a88c4708 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -22,6 +22,10 @@ class Field: """A representation of a type of field in protocol buffers.""" + # Fields are NOT repeated nor maps. + # The RepeatedField overrides this values. + repeated = False + def __init__( self, proto_type, @@ -35,7 +39,7 @@ def __init__( ): # This class is not intended to stand entirely alone; # data is augmented by the metaclass for Message. - self.mcls_data = {} + self.mcls_data = None self.parent = None # If the proto type sent is an object or a string, it is really @@ -59,11 +63,6 @@ def __init__( self.optional = optional self.oneof = oneof - # Fields are neither repeated nor maps. - # The RepeatedField and MapField subclasses override these values - # in their initializers. - self.repeated = False - # Once the descriptor is accessed the first time, cache it. # This is important because in rare cases the message or enum # types are written later. @@ -72,8 +71,8 @@ def __init__( @property def descriptor(self): """Return the descriptor for the field.""" - proto_type = self.proto_type if not self._descriptor: + proto_type = self.proto_type # Resolve the message type, if any, to a string. type_name = None if isinstance(self.message, str): @@ -83,10 +82,11 @@ def descriptor(self): ) type_name = self.message elif self.message: - if hasattr(self.message, "DESCRIPTOR"): - type_name = self.message.DESCRIPTOR.full_name - else: - type_name = self.message.meta.full_name + type_name = ( + self.message.DESCRIPTOR.full_name + if hasattr(self.message, "DESCRIPTOR") + else self.message.meta.full_name + ) elif self.enum: # Nos decipiat. # @@ -146,9 +146,7 @@ def pb_type(self): class RepeatedField(Field): """A representation of a repeated field in protocol buffers.""" - def __init__(self, proto_type, *, number: int, message=None, enum=None): - super().__init__(proto_type, number=number, message=message, enum=enum) - self.repeated = True + repeated = True class MapField(Field): diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index 11c3cf574ba4..2b6faf2f14f0 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -182,7 +182,7 @@ def to_proto(self, proto_type, value, *, strict: bool = False): # Convert lists and tuples recursively. if isinstance(value, (list, tuple)): - return type(value)([self.to_proto(proto_type, i) for i in value],) + return type(value)(self.to_proto(proto_type, i) for i in value) # Convert dictionaries recursively when the proto type is a map. # This is slightly more complicated than converting a list or tuple @@ -196,9 +196,8 @@ def to_proto(self, proto_type, value, *, strict: bool = False): proto_type.DESCRIPTOR.has_options and proto_type.DESCRIPTOR.GetOptions().map_entry ): - return { - k: self.to_proto(type(proto_type().value), v) for k, v in value.items() - } + recursive_type = type(proto_type().value) + return {k: self.to_proto(recursive_type, v) for k, v in value.items()} # Convert ordinary values. rule = self._rules.get(proto_type, self._noop) @@ -235,9 +234,11 @@ def __new__(cls, *, name: str): marshals with the same ``name`` argument will provide the same marshal each time. """ - if name not in cls._instances: - cls._instances[name] = super().__new__(cls) - return cls._instances[name] + klass = cls._instances.get(name) + if klass is None: + klass = cls._instances[name] = super().__new__(cls) + + return klass def __init__(self, *, name: str): """Instantiate a marshal. diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index c853042cde5d..a9a6533215a9 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -60,7 +60,8 @@ def __new__(mcls, name, bases, attrs): # shorthand for a nested message and a repeated field of that message. # Decompose each map into its constituent form. # https://developers.google.com/protocol-buffers/docs/proto3#maps - for key, field in copy.copy(attrs).items(): + map_fields = {} + for key, field in attrs.items(): if not isinstance(field, MapField): continue @@ -92,13 +93,16 @@ def __new__(mcls, name, bases, attrs): entry_attrs["value"] = Field( field.proto_type, number=2, enum=field.enum, message=field.message, ) - attrs[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) + map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) # Create the repeated field for the entry message. - attrs[key] = RepeatedField( - ProtoType.MESSAGE, number=field.number, message=attrs[msg_name], + map_fields[key] = RepeatedField( + ProtoType.MESSAGE, number=field.number, message=map_fields[msg_name], ) + # Add the new entries to the attrs + attrs.update(map_fields) + # Okay, now we deal with all the rest of the fields. # Iterate over all the attributes and separate the fields into # their own sequence. @@ -525,7 +529,7 @@ def __setattr__(self, key, value): For well-known protocol buffer types which are marshalled, either the protocol buffer object or the Python equivalent is accepted. """ - if key.startswith("_"): + if key[0] == "_": return super().__setattr__(key, value) marshal = self._meta.marshal pb_type = self._meta.fields[key].pb_type From 52ca420c1ccca6b5b9bbe3956326d812f3a0c3ee Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2020 16:09:43 -0700 Subject: [PATCH 053/272] chore: release 1.5.1 (#86) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 5064aac980b7..c7c5a718ca12 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.5.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.0...v1.5.1) (2020-07-30) + + +### Bug Fixes + +* numerous small performance tweaks ([#85](https://www.github.com/googleapis/proto-plus-python/issues/85)) ([7b5faf2](https://www.github.com/googleapis/proto-plus-python/commit/7b5faf2e2c20c8022c83d6a99656505aa669200b)) + ## [1.5.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.2...v1.5.0) (2020-07-29) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 9737536c156b..d9cf0a639456 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.5.0" +version = "1.5.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 2dd31950f95d41dc3c0bff97750c234c78e12c7c Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 3 Aug 2020 10:07:11 -0700 Subject: [PATCH 054/272] fix: tweak to_python (#88) --- packages/proto-plus/proto/_file_info.py | 21 +++++-------------- packages/proto-plus/proto/marshal/marshal.py | 7 ++++--- packages/proto-plus/proto/message.py | 22 ++++++++++---------- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 530a3678f56b..0c1e74f00d8d 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -42,28 +42,17 @@ def _get_manifest(self, new_class): def _get_remaining_manifest(self, new_class): return self._get_manifest(new_class) - {new_class.__name__} - def _has_manifest(self, new_class): - return len(self._get_manifest(new_class)) > 0 - - def _is_in_manifest(self, new_class): - return new_class.__name__ in self._get_manifest(new_class) - def _calculate_salt(self, new_class, fallback): - if self._has_manifest(new_class=new_class) and not self._is_in_manifest( - new_class=new_class - ): + manifest = self._get_manifest(new_class) + if manifest and new_class.__name__ not in manifest: log.warning( - "proto-plus module {module} has a declared manifest but {classname} is not in it".format( + "proto-plus module {module} has a declared manifest but {class_name} is not in it".format( module=inspect.getmodule(new_class).__name__, - classname=new_class.__name__, + class_name=new_class.__name__, ) ) - return ( - "" - if self._is_in_manifest(new_class=new_class) - else (fallback or "").lower() - ) + return "" if new_class.__name__ in manifest else (fallback or "").lower() def generate_file_pb(self, new_class, fallback_salt=""): """Generate the descriptors for all protos in the file. diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index 2b6faf2f14f0..f304cefc7f63 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -153,13 +153,14 @@ def reset(self): def to_python(self, proto_type, value, *, absent: bool = None): # Internal protobuf has its own special type for lists of values. # Return a view around it that implements MutableSequence. - if isinstance(value, compat.repeated_composite_types): + value_type = type(value) # Minor performance boost over isinstance + if value_type in compat.repeated_composite_types: return RepeatedComposite(value, marshal=self) - if isinstance(value, compat.repeated_scalar_types): + if value_type in compat.repeated_scalar_types: return Repeated(value, marshal=self) # Same thing for maps of messages. - if isinstance(value, compat.map_composite_types): + if value_type in compat.map_composite_types: return MapComposite(value, marshal=self) # Convert ordinary values. diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index a9a6533215a9..125d8c5fc46e 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -370,20 +370,9 @@ def __init__(self, mapping=None, **kwargs): # * An instance of the underlying protobuf descriptor class. # * A dict # * Nothing (keyword arguments only). - # - # Sanity check: Did we get something not on that list? Error if so. - if mapping and not isinstance( - mapping, (collections.abc.Mapping, type(self), self._meta.pb) - ): - raise TypeError( - "Invalid constructor input for %s: %r" - % (self.__class__.__name__, mapping,) - ) # Handle the first two cases: they both involve keeping # a copy of the underlying protobuf descriptor instance. - if isinstance(mapping, type(self)): - mapping = mapping._pb if isinstance(mapping, self._meta.pb): # Make a copy of the mapping. # This is a constructor for a new object, so users will assume @@ -400,12 +389,23 @@ def __init__(self, mapping=None, **kwargs): if kwargs: self._pb.MergeFrom(self._meta.pb(**kwargs)) return + if isinstance(mapping, type(self)): + # Performance hack to streamline construction from vanilla protos. + self.__init__(mapping=mapping._pb, **kwargs) + return # Handle the remaining case by converging the mapping and kwargs # dictionaries (with kwargs winning), and saving a descriptor # based on that. + if mapping is None: mapping = {} + elif not isinstance(mapping, collections.abc.Mapping): + # Sanity check: Did we get something not a map? Error if so. + raise TypeError( + "Invalid constructor input for %s: %r" + % (self.__class__.__name__, mapping,) + ) mapping.update(kwargs) # Avoid copying the mapping unnecessarily From 4123be60dea356425cab5dcd707ea02df2bf78c3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2020 10:09:23 -0700 Subject: [PATCH 055/272] chore: release 1.5.2 (#89) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index c7c5a718ca12..764a1fe1bd41 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.5.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.1...v1.5.2) (2020-08-03) + + +### Bug Fixes + +* tweak to_python ([#88](https://www.github.com/googleapis/proto-plus-python/issues/88)) ([5459ede](https://www.github.com/googleapis/proto-plus-python/commit/5459ede75597b06df5a211b0e317fb2c1f4b034e)) + ### [1.5.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.0...v1.5.1) (2020-07-30) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index d9cf0a639456..0a40fdd3d04a 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.5.1" +version = "1.5.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From df4aa4bf347c6a39b32a7d9e062a5801483bc92d Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 4 Aug 2020 15:17:25 -0700 Subject: [PATCH 056/272] fix: yet more perf tweaks (#90) Responsible for a 33% performance boost in certain synthetic benchmarks. --- packages/proto-plus/proto/message.py | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 125d8c5fc46e..9cd3d6dd12e4 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -107,18 +107,17 @@ def __new__(mcls, name, bases, attrs): # Iterate over all the attributes and separate the fields into # their own sequence. fields = [] + new_attrs = {} oneofs = collections.OrderedDict() proto_imports = set() index = 0 - for key, field in copy.copy(attrs).items(): + for key, field in attrs.items(): # Sanity check: If this is not a field, do nothing. if not isinstance(field, Field): + # The field objects themselves should not be direct attributes. + new_attrs[key] = field continue - # Remove the field from the attrs dictionary; the field objects - # themselves should not be direct attributes. - attrs.pop(key) - # Add data that the field requires that we do not take in the # constructor because we can derive it from the metaclass. # (The goal is to make the declaration syntax as nice as possible.) @@ -184,7 +183,7 @@ def __new__(mcls, name, bases, attrs): # We determine an appropriate proto filename based on the # Python module. filename = "{0}.proto".format( - attrs.get("__module__", name.lower()).replace(".", "/") + new_attrs.get("__module__", name.lower()).replace(".", "/") ) # Get or create the information about the file, including the @@ -209,7 +208,7 @@ def __new__(mcls, name, bases, attrs): file_info.descriptor.dependency.append(proto_import) # Retrieve any message options. - opts = descriptor_pb2.MessageOptions(**attrs.pop("_pb_options", {})) + opts = descriptor_pb2.MessageOptions(**new_attrs.pop("_pb_options", {})) # Create the underlying proto descriptor. desc = descriptor_pb2.DescriptorProto( @@ -223,9 +222,9 @@ def __new__(mcls, name, bases, attrs): # If any descriptors were nested under this one, they need to be # attached as nested types here. - for child_path in copy.copy(file_info.nested).keys(): - if local_path == child_path[:-1]: - desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path),) + child_paths = [p for p in file_info.nested.keys() if local_path == p[:-1]] + for child_path in child_paths: + desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path)) # Add the descriptor to the file if it is a top-level descriptor, # or to a "holding area" for nested messages otherwise. @@ -235,7 +234,7 @@ def __new__(mcls, name, bases, attrs): file_info.nested[local_path] = desc # Create the MessageInfo instance to be attached to this message. - attrs["_meta"] = _MessageInfo( + new_attrs["_meta"] = _MessageInfo( fields=fields, full_name=full_name, marshal=marshal, @@ -244,7 +243,7 @@ def __new__(mcls, name, bases, attrs): ) # Run the superclass constructor. - cls = super().__new__(mcls, name, bases, attrs) + cls = super().__new__(mcls, name, bases, new_attrs) # The info class and fields need a reference to the class just created. cls._meta.parent = cls @@ -371,9 +370,14 @@ def __init__(self, mapping=None, **kwargs): # * A dict # * Nothing (keyword arguments only). - # Handle the first two cases: they both involve keeping - # a copy of the underlying protobuf descriptor instance. - if isinstance(mapping, self._meta.pb): + if mapping is None: + if not kwargs: + # Special fast path for empty construction. + self._pb = self._meta.pb() + return + + mapping = kwargs + elif isinstance(mapping, self._meta.pb): # Make a copy of the mapping. # This is a constructor for a new object, so users will assume # that it will not have side effects on the arguments being @@ -389,26 +393,22 @@ def __init__(self, mapping=None, **kwargs): if kwargs: self._pb.MergeFrom(self._meta.pb(**kwargs)) return - if isinstance(mapping, type(self)): - # Performance hack to streamline construction from vanilla protos. + elif isinstance(mapping, type(self)): + # Just use the above logic on mapping's underlying pb. self.__init__(mapping=mapping._pb, **kwargs) return - - # Handle the remaining case by converging the mapping and kwargs - # dictionaries (with kwargs winning), and saving a descriptor - # based on that. - - if mapping is None: - mapping = {} elif not isinstance(mapping, collections.abc.Mapping): # Sanity check: Did we get something not a map? Error if so. raise TypeError( "Invalid constructor input for %s: %r" % (self.__class__.__name__, mapping,) ) - mapping.update(kwargs) + else: + # Can't have side effects on mapping. + mapping = copy.copy(mapping) + # kwargs entries take priority for duplicate keys. + mapping.update(kwargs) - # Avoid copying the mapping unnecessarily params = {} # Update the mapping to address any values that need to be # coerced. @@ -572,8 +572,8 @@ def __init__( self.package = package self.full_name = full_name self.options = options - self.fields = collections.OrderedDict([(i.name, i) for i in fields]) - self.fields_by_number = collections.OrderedDict([(i.number, i) for i in fields]) + self.fields = collections.OrderedDict((i.name, i) for i in fields) + self.fields_by_number = collections.OrderedDict((i.number, i) for i in fields) self.marshal = marshal self._pb = None From 0389fa71ecef932296898bacec663a209f66eca1 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2020 15:21:05 -0700 Subject: [PATCH 057/272] chore: release 1.5.3 (#91) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 764a1fe1bd41..a56a0eb43388 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.5.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.2...v1.5.3) (2020-08-04) + + +### Bug Fixes + +* yet more perf tweaks ([#90](https://www.github.com/googleapis/proto-plus-python/issues/90)) ([eb7891c](https://www.github.com/googleapis/proto-plus-python/commit/eb7891cf05124803352b2f4fd719937356bf9167)) + ### [1.5.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.1...v1.5.2) (2020-08-03) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 0a40fdd3d04a..362a3d76ace9 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.5.2" +version = "1.5.3" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 5eeb8784faefc87e35450a6095b7682bf3b352ce Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 5 Aug 2020 16:40:37 -0700 Subject: [PATCH 058/272] feat: more performance optimizations (#92) The two big changes here are bypassing __init__ in a special case and directly setting instance attributes via reaching into self.__dict__. These are optimizations in a potential hot path that were isolated via exhaustive profiling and experimentation and save ~%16 in certain benchmarks. Do not try this at home. --- packages/proto-plus/proto/message.py | 32 +++++++++++------------ packages/proto-plus/tests/test_message.py | 12 +++++++++ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 9cd3d6dd12e4..4873126ca4ee 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -177,7 +177,8 @@ def __new__(mcls, name, bases, attrs): # # m = MyMessage() # MyMessage.field in m - mcls = type("AttrsMeta", (mcls,), opt_attrs) + if opt_attrs: + mcls = type("AttrsMeta", (mcls,), opt_attrs) # Determine the filename. # We determine an appropriate proto filename based on the @@ -295,7 +296,10 @@ def wrap(cls, pb): pb: A protocol buffer object, such as would be returned by :meth:`pb`. """ - return cls(pb, __wrap_original=True) + # Optimized fast path. + instance = cls.__new__(cls) + instance.__dict__["_pb"] = pb + return instance def serialize(cls, instance) -> bytes: """Return the serialized proto. @@ -319,11 +323,7 @@ def deserialize(cls, payload: bytes) -> "Message": ~.Message: An instance of the message class against which this method was called. """ - # Usually we don't wrap the original proto and are force to make a copy - # to prevent modifying user data. - # In this case it's perfectly reasonable to wrap the proto becasue it's - # never user visible, and it gives a huge performance boost. - return cls(cls.pb().FromString(payload), __wrap_original=True) + return cls.wrap(cls.pb().FromString(payload)) def to_json(cls, instance) -> str: """Given a message instance, serialize it to json @@ -373,7 +373,7 @@ def __init__(self, mapping=None, **kwargs): if mapping is None: if not kwargs: # Special fast path for empty construction. - self._pb = self._meta.pb() + self.__dict__["_pb"] = self._meta.pb() return mapping = kwargs @@ -383,15 +383,13 @@ def __init__(self, mapping=None, **kwargs): # that it will not have side effects on the arguments being # passed in. # - # The `__wrap_original` argument is private API to override - # this behavior, because `MessageRule` actually does want to - # wrap the original argument it was given. The `wrap` method - # on the metaclass is the public API for this behavior. - if not kwargs.pop("__wrap_original", False): - mapping = copy.copy(mapping) - self._pb = mapping + # The `wrap` method on the metaclass is the public API for taking + # ownership of the passed in protobuf objet. + mapping = copy.copy(mapping) if kwargs: - self._pb.MergeFrom(self._meta.pb(**kwargs)) + mapping.MergeFrom(self._meta.pb(**kwargs)) + + self.__dict__["_pb"] = mapping return elif isinstance(mapping, type(self)): # Just use the above logic on mapping's underlying pb. @@ -420,7 +418,7 @@ def __init__(self, mapping=None, **kwargs): params[key] = pb_value # Create the internal protocol buffer. - self._pb = self._meta.pb(**params) + self.__dict__["_pb"] = self._meta.pb(**params) def __bool__(self): """Return True if any field is truthy, False otherwise.""" diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index c79dc60b5ad1..b5f598571335 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -216,3 +216,15 @@ class Squid(proto.Message): s = Squid() with pytest.raises(AttributeError): getattr(s, "shell") + + +def test_setattr(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + s1 = Squid() + s2 = Squid(mass_kg=20) + + s1._pb = s2._pb + + assert s1.mass_kg == 20 From f8696b5f8d8b478f4093eda377eee96ecf2db6c4 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2020 16:44:47 -0700 Subject: [PATCH 059/272] chore: release 1.6.0 (#93) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index a56a0eb43388..9a4de9e47b17 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.6.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.3...v1.6.0) (2020-08-05) + + +### Features + +* more performance optimizations ([#92](https://www.github.com/googleapis/proto-plus-python/issues/92)) ([19b1519](https://www.github.com/googleapis/proto-plus-python/commit/19b151960de7c83ac82e670b06cb47d6e885f627)) + ### [1.5.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.2...v1.5.3) (2020-08-04) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 362a3d76ace9..75c3b58c4f41 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.5.3" +version = "1.6.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 8c1f663c14a94148280dc501a3b36fe9c4345002 Mon Sep 17 00:00:00 2001 From: Bob Hancock Date: Fri, 7 Aug 2020 13:43:49 -0400 Subject: [PATCH 060/272] feat: optimize insert for class RepeatedComposite. (#95) Use a standard Python list insert on the target list to replace a loop using pop and extend. Co-authored-by: Bob Hancock --- packages/proto-plus/proto/marshal/collections/repeated.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index 3063e546b5e7..fa51bb5636d7 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -125,10 +125,4 @@ def __setitem__(self, key, value): def insert(self, index: int, value): """Insert ``value`` in the sequence before ``index``.""" pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) - - # Protocol buffers does not define a useful insert, so we have - # to pop everything after this point off the list and reload it. - after = [pb_value] - while self.pb[index:]: - after.append(self.pb.pop(index)) - self.pb.extend(after) + self.pb.insert(index, pb_value) From db4595509c82e8a3a6426b3ec8f0a65d00021e05 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2020 10:56:38 -0700 Subject: [PATCH 061/272] chore: release 1.7.0 (#96) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 9a4de9e47b17..93e020f14645 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.7.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.6.0...v1.7.0) (2020-08-07) + + +### Features + +* optimize insert for class RepeatedComposite. ([#95](https://www.github.com/googleapis/proto-plus-python/issues/95)) ([86790e3](https://www.github.com/googleapis/proto-plus-python/commit/86790e3f7d891e13835699a4e1f50aec6140fa6e)) + ## [1.6.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.3...v1.6.0) (2020-08-05) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 75c3b58c4f41..f47d51335a2e 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.6.0" +version = "1.7.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 605de88e47eba0ef55db50b45e79145a6625a9d9 Mon Sep 17 00:00:00 2001 From: Bob Hancock Date: Mon, 17 Aug 2020 15:43:56 +0000 Subject: [PATCH 062/272] fix: revert algorithm for RepeatedComposite insertion. (#101) The sequence pased is referred to as a list but it is not guarantted to be a Python native list. Co-authored-by: Bob Hancock --- packages/proto-plus/proto/marshal/collections/repeated.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index fa51bb5636d7..3063e546b5e7 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -125,4 +125,10 @@ def __setitem__(self, key, value): def insert(self, index: int, value): """Insert ``value`` in the sequence before ``index``.""" pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) - self.pb.insert(index, pb_value) + + # Protocol buffers does not define a useful insert, so we have + # to pop everything after this point off the list and reload it. + after = [pb_value] + while self.pb[index:]: + after.append(self.pb.pop(index)) + self.pb.extend(after) From 0fe99c72174c0f3c3e4fbcbd8dbcb9a4de5749c2 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2020 09:26:23 -0700 Subject: [PATCH 063/272] chore: release 1.7.1 (#102) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 93e020f14645..449c1f3b3c54 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.7.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.7.0...v1.7.1) (2020-08-17) + + +### Bug Fixes + +* revert algorithm for RepeatedComposite insertion. ([#101](https://www.github.com/googleapis/proto-plus-python/issues/101)) ([ae946aa](https://www.github.com/googleapis/proto-plus-python/commit/ae946aa2a3b394fa31590224fcf50593bde0ccaa)) + ## [1.7.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.6.0...v1.7.0) (2020-08-07) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index f47d51335a2e..e65d8f5158b4 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.7.0" +version = "1.7.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From c0a23fad68fb4eb779b310787cd9265552d943ab Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 28 Aug 2020 11:43:11 -0700 Subject: [PATCH 064/272] feat: allow enum strings in json serialization and deserialization (#107) * feat: allow enum strings in json serialization and deserialization For protobuf messages that contain enum fields, it is now possible to specify that enum variants should be serialized as names and not as integers. E.g. json_str = MyMessage.to_json(my_message, enum_strings=True) Similarly, serialization from json that uses this convention is now supported. This is useful for interoperation with other data sources that do use strings to define enum variants in json serialization; and for debugging, where visually inspecting data structures can be helpful, and variant names are more informative than numerical values. Note: includes reformatting of many source files due to an update to Black --- packages/proto-plus/.circleci/config.yml | 2 +- packages/proto-plus/docs/messages.rst | 9 +++ packages/proto-plus/proto/_file_info.py | 25 +++++++- packages/proto-plus/proto/enums.py | 49 ++++++++++++++ packages/proto-plus/proto/fields.py | 17 +---- packages/proto-plus/proto/marshal/marshal.py | 3 +- .../proto-plus/proto/marshal/rules/dates.py | 3 +- .../proto-plus/proto/marshal/rules/enums.py | 3 +- .../proto-plus/proto/marshal/rules/struct.py | 12 +++- packages/proto-plus/proto/message.py | 57 ++++++++++------- packages/proto-plus/proto/modules.py | 9 ++- packages/proto-plus/tests/conftest.py | 4 +- packages/proto-plus/tests/test_fields_enum.py | 13 ++++ .../tests/test_fields_map_composite.py | 35 ++++++++-- .../tests/test_fields_repeated_composite.py | 4 +- .../tests/test_file_info_salting.py | 4 +- .../test_file_info_salting_with_manifest.py | 9 ++- packages/proto-plus/tests/test_json.py | 38 +++++++++++ .../tests/test_marshal_types_dates.py | 64 +++++++++++++++---- .../tests/test_marshal_types_wrappers_bool.py | 30 +++++++-- ...sage_filename_with_and_without_manifest.py | 5 +- .../test_message_filename_with_manifest.py | 5 +- .../proto-plus/tests/test_message_nested.py | 3 +- packages/proto-plus/tests/test_modules.py | 8 ++- 24 files changed, 332 insertions(+), 79 deletions(-) diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index e3c4aeb7f71d..2dbc5b45255c 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -249,7 +249,7 @@ jobs: - run: name: Format files command: | - black . + black -l 88 . - run: name: Check diff command: | diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index 18dd4db60bef..aa37c6e1165b 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -165,3 +165,12 @@ via the :meth:`~.Message.to_json` and :meth:`~.Message.from_json` methods. new_song = Song.from_json(json) +The behavior of JSON serialization can be customized to use strings to +represent enum values. + +.. code-block:: python + + song = Song(genre=Genre.JAZZ) + json = Song.to_json(song, use_integers_for_enums=False) + + assert "JAZZ" in json diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 0c1e74f00d8d..a620281813c1 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import collections.abc +import collections import inspect import logging +from google.protobuf import descriptor_pb2 from google.protobuf import descriptor_pool from google.protobuf import message from google.protobuf import reflection @@ -32,6 +33,28 @@ class _FileInfo( ): registry = {} # Mapping[str, '_FileInfo'] + @classmethod + def maybe_add_descriptor(cls, filename, package): + descriptor = cls.registry.get(filename) + if not descriptor: + descriptor = cls.registry[filename] = cls( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, + package=package, + syntax="proto3", + ), + enums=collections.OrderedDict(), + messages=collections.OrderedDict(), + name=filename, + nested={}, + ) + + return descriptor + + @staticmethod + def proto_file_name(name): + return "{0}.proto".format(name).replace(".", "/") + def _get_manifest(self, new_class): module = inspect.getmodule(new_class) if hasattr(module, "__protobuf__"): diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index ef1af5912a1d..4144d33cce20 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -14,6 +14,9 @@ import enum +from google.protobuf import descriptor_pb2 + +from proto import _file_info from proto import _package_info from proto.marshal.rules.enums import EnumRule @@ -30,9 +33,49 @@ def __new__(mcls, name, bases, attrs): # this component belongs within the file. package, marshal = _package_info.compile(name, attrs) + # Determine the local path of this proto component within the file. + local_path = tuple(attrs.get("__qualname__", name).split(".")) + + # Sanity check: We get the wrong full name if a class is declared + # inside a function local scope; correct this. + if "" in local_path: + ix = local_path.index("") + local_path = local_path[: ix - 1] + local_path[ix + 1 :] + + # Determine the full name in protocol buffers. + # The C++ proto implementation doesn't like dots in names, so use underscores. + full_name = "_".join((package,) + local_path).lstrip("_") + enum_desc = descriptor_pb2.EnumDescriptorProto( + name=full_name, + # Note: the superclass ctor removes the variants, so get them now. + # Note: proto3 requires that the first variant value be zero. + value=sorted( + ( + descriptor_pb2.EnumValueDescriptorProto(name=name, number=number) + # Minor hack to get all the enum variants out. + for name, number in attrs.items() + if isinstance(number, int) + ), + key=lambda v: v.number, + ), + ) + + filename = _file_info._FileInfo.proto_file_name( + attrs.get("__module__", name.lower()) + ) + + file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) + file_info.descriptor.enum_type.add().MergeFrom(enum_desc) + # Run the superclass constructor. cls = super().__new__(mcls, name, bases, attrs) + # We can't just add a "_meta" element to attrs because the Enum + # machinery doesn't know what to do with a non-int value. + cls._meta = _EnumInfo(full_name=full_name, pb=enum_desc) + + file_info.enums[full_name] = cls + # Register the enum with the marshal. marshal.register(cls, EnumRule(cls)) @@ -44,3 +87,9 @@ class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): """A enum object that also builds a protobuf enum descriptor.""" pass + + +class _EnumInfo: + def __init__(self, *, full_name: str, pb): + self.full_name = full_name + self.pb = pb diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index c951a88c4708..350cec9c7aa0 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -78,7 +78,8 @@ def descriptor(self): if isinstance(self.message, str): if not self.message.startswith(self.package): self.message = "{package}.{name}".format( - package=self.package, name=self.message, + package=self.package, + name=self.message, ) type_name = self.message elif self.message: @@ -88,19 +89,7 @@ def descriptor(self): else self.message.meta.full_name ) elif self.enum: - # Nos decipiat. - # - # As far as the wire format is concerned, enums are int32s. - # Protocol buffers itself also only sends ints; the enum - # objects are simply helper classes for translating names - # and values and it is the user's job to resolve to an int. - # - # Therefore, the non-trivial effort of adding the actual - # enum descriptors seems to add little or no actual value. - # - # FIXME: Eventually, come back and put in the actual enum - # descriptors. - proto_type = ProtoType.INT32 + type_name = self.enum._meta.full_name # Set the descriptor. self._descriptor = descriptor_pb2.FieldDescriptorProto( diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index f304cefc7f63..0ca843cc6d2d 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -209,7 +209,8 @@ def to_proto(self, proto_type, value, *, strict: bool = False): raise TypeError( "Parameter must be instance of the same class; " "expected {expected}, got {got}".format( - expected=proto_type.__name__, got=pb_value.__class__.__name__, + expected=proto_type.__name__, + got=pb_value.__class__.__name__, ), ) diff --git a/packages/proto-plus/proto/marshal/rules/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py index 1e0695ec2f9e..5145bcf84ae4 100644 --- a/packages/proto-plus/proto/marshal/rules/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -44,7 +44,8 @@ def to_proto(self, value) -> timestamp_pb2.Timestamp: return value.timestamp_pb() if isinstance(value, datetime): return timestamp_pb2.Timestamp( - seconds=int(value.timestamp()), nanos=value.microsecond * 1000, + seconds=int(value.timestamp()), + nanos=value.microsecond * 1000, ) return value diff --git a/packages/proto-plus/proto/marshal/rules/enums.py b/packages/proto-plus/proto/marshal/rules/enums.py index d965666c59c0..9cfc312764b6 100644 --- a/packages/proto-plus/proto/marshal/rules/enums.py +++ b/packages/proto-plus/proto/marshal/rules/enums.py @@ -36,7 +36,8 @@ def to_python(self, value, *, absent: bool = None): # the user realizes that an unexpected value came along. warnings.warn( "Unrecognized {name} enum value: {value}".format( - name=self._enum.__name__, value=value, + name=self._enum.__name__, + value=value, ) ) return value diff --git a/packages/proto-plus/proto/marshal/rules/struct.py b/packages/proto-plus/proto/marshal/rules/struct.py index dc183407c026..e8928d82e64f 100644 --- a/packages/proto-plus/proto/marshal/rules/struct.py +++ b/packages/proto-plus/proto/marshal/rules/struct.py @@ -43,11 +43,15 @@ def to_python(self, value, *, absent: bool = None): return str(value.string_value) if kind == "struct_value": return self._marshal.to_python( - struct_pb2.Struct, value.struct_value, absent=False, + struct_pb2.Struct, + value.struct_value, + absent=False, ) if kind == "list_value": return self._marshal.to_python( - struct_pb2.ListValue, value.list_value, absent=False, + struct_pb2.ListValue, + value.list_value, + absent=False, ) raise AttributeError @@ -114,7 +118,9 @@ def to_proto(self, value) -> struct_pb2.Struct: if isinstance(value, struct_pb2.Struct): return value if isinstance(value, maps.MapComposite): - return struct_pb2.Struct(fields={k: v for k, v in value.pb.items()},) + return struct_pb2.Struct( + fields={k: v for k, v in value.pb.items()}, + ) # We got a dict (or something dict-like); convert it. answer = struct_pb2.Struct( diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 4873126ca4ee..9470e036474f 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -68,7 +68,9 @@ def __new__(mcls, name, bases, attrs): # Determine the name of the entry message. msg_name = "{pascal_key}Entry".format( pascal_key=re.sub( - r"_\w", lambda m: m.group()[1:].upper(), key, + r"_\w", + lambda m: m.group()[1:].upper(), + key, ).replace(key[0], key[0].upper(), 1), ) @@ -84,20 +86,26 @@ def __new__(mcls, name, bases, attrs): { "__module__": attrs.get("__module__", None), "__qualname__": "{prefix}.{name}".format( - prefix=attrs.get("__qualname__", name), name=msg_name, + prefix=attrs.get("__qualname__", name), + name=msg_name, ), "_pb_options": {"map_entry": True}, } ) entry_attrs["key"] = Field(field.map_key_type, number=1) entry_attrs["value"] = Field( - field.proto_type, number=2, enum=field.enum, message=field.message, + field.proto_type, + number=2, + enum=field.enum, + message=field.message, ) map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) # Create the repeated field for the entry message. map_fields[key] = RepeatedField( - ProtoType.MESSAGE, number=field.number, message=map_fields[msg_name], + ProtoType.MESSAGE, + number=field.number, + message=map_fields[msg_name], ) # Add the new entries to the attrs @@ -183,24 +191,13 @@ def __new__(mcls, name, bases, attrs): # Determine the filename. # We determine an appropriate proto filename based on the # Python module. - filename = "{0}.proto".format( - new_attrs.get("__module__", name.lower()).replace(".", "/") + filename = _file_info._FileInfo.proto_file_name( + new_attrs.get("__module__", name.lower()) ) # Get or create the information about the file, including the # descriptor to which the new message descriptor shall be added. - file_info = _file_info._FileInfo.registry.setdefault( - filename, - _file_info._FileInfo( - descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", - ), - enums=collections.OrderedDict(), - messages=collections.OrderedDict(), - name=filename, - nested={}, - ), - ) + file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) # Ensure any imports that would be necessary are assigned to the file # descriptor proto being created. @@ -286,7 +283,13 @@ def pb(cls, obj=None, *, coerce: bool = False): if coerce: obj = cls(obj) else: - raise TypeError("%r is not an instance of %s" % (obj, cls.__name__,)) + raise TypeError( + "%r is not an instance of %s" + % ( + obj, + cls.__name__, + ) + ) return obj._pb def wrap(cls, pb): @@ -325,17 +328,24 @@ def deserialize(cls, payload: bytes) -> "Message": """ return cls.wrap(cls.pb().FromString(payload)) - def to_json(cls, instance) -> str: + def to_json(cls, instance, *, use_integers_for_enums=True) -> str: """Given a message instance, serialize it to json Args: instance: An instance of this message type, or something 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. Returns: str: The json string representation of the protocol buffer. """ - return MessageToJson(cls.pb(instance)) + return MessageToJson( + cls.pb(instance), + use_integers_for_enums=use_integers_for_enums, + including_default_value_fields=True, + ) def from_json(cls, payload) -> "Message": """Given a json string representing an instance, @@ -399,7 +409,10 @@ def __init__(self, mapping=None, **kwargs): # Sanity check: Did we get something not a map? Error if so. raise TypeError( "Invalid constructor input for %s: %r" - % (self.__class__.__name__, mapping,) + % ( + self.__class__.__name__, + mapping, + ) ) else: # Can't have side effects on mapping. diff --git a/packages/proto-plus/proto/modules.py b/packages/proto-plus/proto/modules.py index 224213f40d6c..7c752d6971db 100644 --- a/packages/proto-plus/proto/modules.py +++ b/packages/proto-plus/proto/modules.py @@ -17,7 +17,8 @@ _ProtoModule = collections.namedtuple( - "ProtoModule", ["package", "marshal", "manifest"], + "ProtoModule", + ["package", "marshal", "manifest"], ) @@ -39,7 +40,11 @@ def define_module( """ if not marshal: marshal = package - return _ProtoModule(package=package, marshal=marshal, manifest=frozenset(manifest),) + return _ProtoModule( + package=package, + marshal=marshal, + manifest=frozenset(manifest), + ) __all__ = ("define_module",) diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index 2cf373527c00..ad6e899fdc8f 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -85,7 +85,9 @@ def _register_messages(scope, iterable, sym_db): """Create and register messages from the file descriptor.""" for name, descriptor in iterable.items(): new_msg = reflection.GeneratedProtocolMessageType( - name, (message.Message,), {"DESCRIPTOR": descriptor, "__module__": None}, + name, + (message.Message,), + {"DESCRIPTOR": descriptor, "__module__": None}, ) sym_db.RegisterMessage(new_msg) setattr(scope, name, new_msg) diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index b494bd0c9adc..1471abdd1d0c 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -252,3 +252,16 @@ class Foo(proto.Message): assert "color" not in foo assert not foo.color assert Foo.pb(foo).color == 0 + + +class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + ABYSSOPELAGIC = 2 + HADOPELAGIC = 3 + + +def test_enum_outest(): + z = Zone(value=Zone.MESOPELAGIC) + + assert z == Zone.MESOPELAGIC diff --git a/packages/proto-plus/tests/test_fields_map_composite.py b/packages/proto-plus/tests/test_fields_map_composite.py index 13bf0c5f6bf2..3b2d8fd9acd6 100644 --- a/packages/proto-plus/tests/test_fields_map_composite.py +++ b/packages/proto-plus/tests/test_fields_map_composite.py @@ -22,7 +22,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz(foos={"i": Foo(bar=42), "j": Foo(bar=24)}) assert len(baz.foos) == 2 @@ -36,7 +41,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz(foos={"i": {"bar": 42}, "j": {"bar": 24}}) assert len(baz.foos) == 2 @@ -52,7 +62,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz() baz.foos["i"] = Foo(bar=42) @@ -68,7 +83,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz() baz.foos["i"] = Foo() @@ -82,7 +102,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz() baz.foos["i"] = Foo(bar=42) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index 2990eea6597b..d64675200815 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -74,7 +74,9 @@ class Baz(proto.Message): def test_repeated_composite_marshaled(): class Foo(proto.Message): timestamps = proto.RepeatedField( - proto.MESSAGE, message=timestamp_pb2.Timestamp, number=1, + proto.MESSAGE, + message=timestamp_pb2.Timestamp, + number=1, ) foo = Foo( diff --git a/packages/proto-plus/tests/test_file_info_salting.py b/packages/proto-plus/tests/test_file_info_salting.py index e87731cfbe39..15afdc08d70e 100644 --- a/packages/proto-plus/tests/test_file_info_salting.py +++ b/packages/proto-plus/tests/test_file_info_salting.py @@ -34,7 +34,9 @@ def sample_file_info(name): filename, _file_info._FileInfo( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", + name=filename, + package=package, + syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), diff --git a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py index 0a0e7c579e99..43380aa6197c 100644 --- a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py +++ b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py @@ -21,7 +21,10 @@ from proto import _file_info, _package_info PACKAGE = "a.test.package.salting.with.manifest" -__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) +__protobuf__ = proto.module( + package=PACKAGE, + manifest={"This", "That"}, +) class This(proto.Message): @@ -49,7 +52,9 @@ def sample_file_info(name): filename, _file_info._FileInfo( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", + name=filename, + package=package, + syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index a300a84b8f26..b4ffcf17e3fb 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -50,3 +50,41 @@ class Squid(proto.Message): s2 = Squid.from_json(json) assert s == s2 + + +def test_stringy_enums(): + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + class Squid(proto.Message): + zone = proto.Field(Zone, number=1) + + s1 = Squid(zone=Zone.MESOPELAGIC) + json = Squid.to_json(s1, use_integers_for_enums=False) + + assert "MESOPELAGIC" in json + + s2 = Squid.from_json(json) + assert s2.zone == s1.zone + + +def test_default_enums(): + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + class Squid(proto.Message): + zone = proto.Field(Zone, number=1) + + s = Squid() + assert s.zone == Zone.EPIPELAGIC + json1 = Squid.to_json(s).replace(" ", "").replace("\n", "") + assert json1 == '{"zone":0}' + + json2 = Squid.to_json(s, use_integers_for_enums=False) + assert "EPIPELAGIC" in json2 diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 631858935226..5fe09ab5c303 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -28,7 +28,9 @@ def test_timestamp_read(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=timestamp_pb2.Timestamp(seconds=1335020400)) @@ -45,7 +47,9 @@ class Foo(proto.Message): def test_timestamp_write_init(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) @@ -60,7 +64,9 @@ class Foo(proto.Message): def test_timestamp_write(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -77,7 +83,9 @@ class Foo(proto.Message): def test_timestamp_write_pb2(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -93,7 +101,9 @@ class Foo(proto.Message): def test_timestamp_rmw_nanos(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -110,7 +120,9 @@ class Foo(proto.Message): def test_timestamp_absence(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -120,7 +132,9 @@ class Foo(proto.Message): def test_timestamp_del(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) @@ -130,7 +144,11 @@ class Foo(proto.Message): def test_duration_read(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=duration_pb2.Duration(seconds=60, nanos=1000)) assert isinstance(foo.ttl, timedelta) @@ -142,7 +160,11 @@ class Foo(proto.Message): def test_duration_write_init(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=timedelta(days=2)) assert isinstance(foo.ttl, timedelta) @@ -155,7 +177,11 @@ class Foo(proto.Message): def test_duration_write(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo() foo.ttl = timedelta(seconds=120) @@ -167,7 +193,11 @@ class Foo(proto.Message): def test_duration_write_pb2(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo() foo.ttl = duration_pb2.Duration(seconds=120) @@ -179,7 +209,11 @@ class Foo(proto.Message): def test_duration_del(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=timedelta(seconds=900)) del foo.ttl @@ -191,7 +225,11 @@ class Foo(proto.Message): def test_duration_nanos_rmw(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=timedelta(microseconds=50)) assert foo.ttl.microseconds == 50 diff --git a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py index ffe206a696f9..e27caf990dcb 100644 --- a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py +++ b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py @@ -20,7 +20,11 @@ def test_bool_value_init(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) assert Foo(bar=True).bar is True assert Foo(bar=False).bar is False @@ -29,7 +33,11 @@ class Foo(proto.Message): def test_bool_value_init_dict(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) assert Foo({"bar": True}).bar is True assert Foo({"bar": False}).bar is False @@ -38,7 +46,11 @@ class Foo(proto.Message): def test_bool_value_distinction_from_bool(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) baz = proto.Field(proto.BOOL, number=2) assert Foo().bar is None @@ -63,7 +75,11 @@ class Foo(proto.Message): def test_bool_value_write_bool_value(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) foo = Foo(bar=True) foo.bar = wrappers_pb2.BoolValue() @@ -72,7 +88,11 @@ class Foo(proto.Message): def test_bool_value_del(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) foo = Foo(bar=False) assert foo.bar is False diff --git a/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py index 32c2bc15ecaf..e67e8472b830 100644 --- a/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py +++ b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py @@ -16,7 +16,10 @@ PACKAGE = "a.test.package.with.and.without.manifest" -__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) +__protobuf__ = proto.module( + package=PACKAGE, + manifest={"This", "That"}, +) class This(proto.Message): diff --git a/packages/proto-plus/tests/test_message_filename_with_manifest.py b/packages/proto-plus/tests/test_message_filename_with_manifest.py index e12e5a511b6f..aa96028ef792 100644 --- a/packages/proto-plus/tests/test_message_filename_with_manifest.py +++ b/packages/proto-plus/tests/test_message_filename_with_manifest.py @@ -15,7 +15,10 @@ import proto PACKAGE = "a.test.package.with.manifest" -__protobuf__ = proto.module(package=PACKAGE, manifest={"ThisFoo", "ThisBar"},) +__protobuf__ = proto.module( + package=PACKAGE, + manifest={"ThisFoo", "ThisBar"}, +) class ThisFoo(proto.Message): diff --git a/packages/proto-plus/tests/test_message_nested.py b/packages/proto-plus/tests/test_message_nested.py index 89dfad68610d..41af9507f22e 100644 --- a/packages/proto-plus/tests/test_message_nested.py +++ b/packages/proto-plus/tests/test_message_nested.py @@ -56,7 +56,8 @@ class Bacon(proto.Message): baz = proto.Field(proto.MESSAGE, number=2, message=Baz) foo = Foo( - bar={"spam": "xyz", "eggs": False}, baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), + bar={"spam": "xyz", "eggs": False}, + baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), ) assert foo.bar.spam == "xyz" assert not foo.bar.eggs diff --git a/packages/proto-plus/tests/test_modules.py b/packages/proto-plus/tests/test_modules.py index 897ce46cf075..79a99da1fb00 100644 --- a/packages/proto-plus/tests/test_modules.py +++ b/packages/proto-plus/tests/test_modules.py @@ -38,7 +38,8 @@ class Foo(proto.Message): def test_module_package_explicit_marshal(): sys.modules[__name__].__protobuf__ = proto.module( - package="spam.eggs.v1", marshal="foo", + package="spam.eggs.v1", + marshal="foo", ) try: @@ -54,7 +55,10 @@ class Foo(proto.Message): def test_module_manifest(): - __protobuf__ = proto.module(manifest={"Foo", "Bar", "Baz"}, package="spam.eggs.v1",) + __protobuf__ = proto.module( + manifest={"Foo", "Bar", "Baz"}, + package="spam.eggs.v1", + ) # We want to fake a module, but modules have attribute access, and # `frame.f_locals` is a dictionary. Since we only actually care about From 57f98e4cdf39649b868e6c0f3d4a3fc5048b7fd5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2020 11:46:52 -0700 Subject: [PATCH 065/272] chore: release 1.8.0 (#108) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 449c1f3b3c54..20dd2f04399a 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.8.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.7.1...v1.8.0) (2020-08-28) + + +### Features + +* allow enum strings in json serialization and deserialization ([#107](https://www.github.com/googleapis/proto-plus-python/issues/107)) ([a082f85](https://www.github.com/googleapis/proto-plus-python/commit/a082f85ffcb72e2c53c0e33e40e6df2927a41259)) + ### [1.7.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.7.0...v1.7.1) (2020-08-17) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index e65d8f5158b4..51ce0c643d6f 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.7.1" +version = "1.8.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From aa1afa9446fc849a94170e7ed198f2286e236719 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 28 Aug 2020 16:26:41 -0600 Subject: [PATCH 066/272] fix: revert "feat: allow enum strings in json serialization and deserialization" (#110) * Revert "feat: allow enum strings in json serialization and deserialization (#107)" This reverts commit a082f85ffcb72e2c53c0e33e40e6df2927a41259. --- packages/proto-plus/.circleci/config.yml | 4 +- packages/proto-plus/docs/messages.rst | 9 --- packages/proto-plus/proto/_file_info.py | 25 +------- packages/proto-plus/proto/enums.py | 49 -------------- packages/proto-plus/proto/fields.py | 17 ++++- packages/proto-plus/proto/marshal/marshal.py | 3 +- .../proto-plus/proto/marshal/rules/dates.py | 3 +- .../proto-plus/proto/marshal/rules/enums.py | 3 +- .../proto-plus/proto/marshal/rules/struct.py | 12 +--- packages/proto-plus/proto/message.py | 57 +++++++---------- packages/proto-plus/proto/modules.py | 9 +-- packages/proto-plus/tests/conftest.py | 4 +- packages/proto-plus/tests/test_fields_enum.py | 13 ---- .../tests/test_fields_map_composite.py | 35 ++-------- .../tests/test_fields_repeated_composite.py | 4 +- .../tests/test_file_info_salting.py | 4 +- .../test_file_info_salting_with_manifest.py | 9 +-- packages/proto-plus/tests/test_json.py | 38 ----------- .../tests/test_marshal_types_dates.py | 64 ++++--------------- .../tests/test_marshal_types_wrappers_bool.py | 30 ++------- ...sage_filename_with_and_without_manifest.py | 5 +- .../test_message_filename_with_manifest.py | 5 +- .../proto-plus/tests/test_message_nested.py | 3 +- packages/proto-plus/tests/test_modules.py | 8 +-- 24 files changed, 80 insertions(+), 333 deletions(-) diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index 2dbc5b45255c..d1b2ab01e162 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -245,11 +245,11 @@ jobs: - run: name: Install black command: | - pip install black + pip install black==19.10b0 - run: name: Format files command: | - black -l 88 . + black . - run: name: Check diff command: | diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index aa37c6e1165b..18dd4db60bef 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -165,12 +165,3 @@ via the :meth:`~.Message.to_json` and :meth:`~.Message.from_json` methods. new_song = Song.from_json(json) -The behavior of JSON serialization can be customized to use strings to -represent enum values. - -.. code-block:: python - - song = Song(genre=Genre.JAZZ) - json = Song.to_json(song, use_integers_for_enums=False) - - assert "JAZZ" in json diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index a620281813c1..0c1e74f00d8d 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -12,11 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import collections +import collections.abc import inspect import logging -from google.protobuf import descriptor_pb2 from google.protobuf import descriptor_pool from google.protobuf import message from google.protobuf import reflection @@ -33,28 +32,6 @@ class _FileInfo( ): registry = {} # Mapping[str, '_FileInfo'] - @classmethod - def maybe_add_descriptor(cls, filename, package): - descriptor = cls.registry.get(filename) - if not descriptor: - descriptor = cls.registry[filename] = cls( - descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, - package=package, - syntax="proto3", - ), - enums=collections.OrderedDict(), - messages=collections.OrderedDict(), - name=filename, - nested={}, - ) - - return descriptor - - @staticmethod - def proto_file_name(name): - return "{0}.proto".format(name).replace(".", "/") - def _get_manifest(self, new_class): module = inspect.getmodule(new_class) if hasattr(module, "__protobuf__"): diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 4144d33cce20..ef1af5912a1d 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -14,9 +14,6 @@ import enum -from google.protobuf import descriptor_pb2 - -from proto import _file_info from proto import _package_info from proto.marshal.rules.enums import EnumRule @@ -33,49 +30,9 @@ def __new__(mcls, name, bases, attrs): # this component belongs within the file. package, marshal = _package_info.compile(name, attrs) - # Determine the local path of this proto component within the file. - local_path = tuple(attrs.get("__qualname__", name).split(".")) - - # Sanity check: We get the wrong full name if a class is declared - # inside a function local scope; correct this. - if "" in local_path: - ix = local_path.index("") - local_path = local_path[: ix - 1] + local_path[ix + 1 :] - - # Determine the full name in protocol buffers. - # The C++ proto implementation doesn't like dots in names, so use underscores. - full_name = "_".join((package,) + local_path).lstrip("_") - enum_desc = descriptor_pb2.EnumDescriptorProto( - name=full_name, - # Note: the superclass ctor removes the variants, so get them now. - # Note: proto3 requires that the first variant value be zero. - value=sorted( - ( - descriptor_pb2.EnumValueDescriptorProto(name=name, number=number) - # Minor hack to get all the enum variants out. - for name, number in attrs.items() - if isinstance(number, int) - ), - key=lambda v: v.number, - ), - ) - - filename = _file_info._FileInfo.proto_file_name( - attrs.get("__module__", name.lower()) - ) - - file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) - file_info.descriptor.enum_type.add().MergeFrom(enum_desc) - # Run the superclass constructor. cls = super().__new__(mcls, name, bases, attrs) - # We can't just add a "_meta" element to attrs because the Enum - # machinery doesn't know what to do with a non-int value. - cls._meta = _EnumInfo(full_name=full_name, pb=enum_desc) - - file_info.enums[full_name] = cls - # Register the enum with the marshal. marshal.register(cls, EnumRule(cls)) @@ -87,9 +44,3 @@ class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): """A enum object that also builds a protobuf enum descriptor.""" pass - - -class _EnumInfo: - def __init__(self, *, full_name: str, pb): - self.full_name = full_name - self.pb = pb diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index 350cec9c7aa0..c951a88c4708 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -78,8 +78,7 @@ def descriptor(self): if isinstance(self.message, str): if not self.message.startswith(self.package): self.message = "{package}.{name}".format( - package=self.package, - name=self.message, + package=self.package, name=self.message, ) type_name = self.message elif self.message: @@ -89,7 +88,19 @@ def descriptor(self): else self.message.meta.full_name ) elif self.enum: - type_name = self.enum._meta.full_name + # Nos decipiat. + # + # As far as the wire format is concerned, enums are int32s. + # Protocol buffers itself also only sends ints; the enum + # objects are simply helper classes for translating names + # and values and it is the user's job to resolve to an int. + # + # Therefore, the non-trivial effort of adding the actual + # enum descriptors seems to add little or no actual value. + # + # FIXME: Eventually, come back and put in the actual enum + # descriptors. + proto_type = ProtoType.INT32 # Set the descriptor. self._descriptor = descriptor_pb2.FieldDescriptorProto( diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index 0ca843cc6d2d..f304cefc7f63 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -209,8 +209,7 @@ def to_proto(self, proto_type, value, *, strict: bool = False): raise TypeError( "Parameter must be instance of the same class; " "expected {expected}, got {got}".format( - expected=proto_type.__name__, - got=pb_value.__class__.__name__, + expected=proto_type.__name__, got=pb_value.__class__.__name__, ), ) diff --git a/packages/proto-plus/proto/marshal/rules/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py index 5145bcf84ae4..1e0695ec2f9e 100644 --- a/packages/proto-plus/proto/marshal/rules/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -44,8 +44,7 @@ def to_proto(self, value) -> timestamp_pb2.Timestamp: return value.timestamp_pb() if isinstance(value, datetime): return timestamp_pb2.Timestamp( - seconds=int(value.timestamp()), - nanos=value.microsecond * 1000, + seconds=int(value.timestamp()), nanos=value.microsecond * 1000, ) return value diff --git a/packages/proto-plus/proto/marshal/rules/enums.py b/packages/proto-plus/proto/marshal/rules/enums.py index 9cfc312764b6..d965666c59c0 100644 --- a/packages/proto-plus/proto/marshal/rules/enums.py +++ b/packages/proto-plus/proto/marshal/rules/enums.py @@ -36,8 +36,7 @@ def to_python(self, value, *, absent: bool = None): # the user realizes that an unexpected value came along. warnings.warn( "Unrecognized {name} enum value: {value}".format( - name=self._enum.__name__, - value=value, + name=self._enum.__name__, value=value, ) ) return value diff --git a/packages/proto-plus/proto/marshal/rules/struct.py b/packages/proto-plus/proto/marshal/rules/struct.py index e8928d82e64f..dc183407c026 100644 --- a/packages/proto-plus/proto/marshal/rules/struct.py +++ b/packages/proto-plus/proto/marshal/rules/struct.py @@ -43,15 +43,11 @@ def to_python(self, value, *, absent: bool = None): return str(value.string_value) if kind == "struct_value": return self._marshal.to_python( - struct_pb2.Struct, - value.struct_value, - absent=False, + struct_pb2.Struct, value.struct_value, absent=False, ) if kind == "list_value": return self._marshal.to_python( - struct_pb2.ListValue, - value.list_value, - absent=False, + struct_pb2.ListValue, value.list_value, absent=False, ) raise AttributeError @@ -118,9 +114,7 @@ def to_proto(self, value) -> struct_pb2.Struct: if isinstance(value, struct_pb2.Struct): return value if isinstance(value, maps.MapComposite): - return struct_pb2.Struct( - fields={k: v for k, v in value.pb.items()}, - ) + return struct_pb2.Struct(fields={k: v for k, v in value.pb.items()},) # We got a dict (or something dict-like); convert it. answer = struct_pb2.Struct( diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 9470e036474f..4873126ca4ee 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -68,9 +68,7 @@ def __new__(mcls, name, bases, attrs): # Determine the name of the entry message. msg_name = "{pascal_key}Entry".format( pascal_key=re.sub( - r"_\w", - lambda m: m.group()[1:].upper(), - key, + r"_\w", lambda m: m.group()[1:].upper(), key, ).replace(key[0], key[0].upper(), 1), ) @@ -86,26 +84,20 @@ def __new__(mcls, name, bases, attrs): { "__module__": attrs.get("__module__", None), "__qualname__": "{prefix}.{name}".format( - prefix=attrs.get("__qualname__", name), - name=msg_name, + prefix=attrs.get("__qualname__", name), name=msg_name, ), "_pb_options": {"map_entry": True}, } ) entry_attrs["key"] = Field(field.map_key_type, number=1) entry_attrs["value"] = Field( - field.proto_type, - number=2, - enum=field.enum, - message=field.message, + field.proto_type, number=2, enum=field.enum, message=field.message, ) map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) # Create the repeated field for the entry message. map_fields[key] = RepeatedField( - ProtoType.MESSAGE, - number=field.number, - message=map_fields[msg_name], + ProtoType.MESSAGE, number=field.number, message=map_fields[msg_name], ) # Add the new entries to the attrs @@ -191,13 +183,24 @@ def __new__(mcls, name, bases, attrs): # Determine the filename. # We determine an appropriate proto filename based on the # Python module. - filename = _file_info._FileInfo.proto_file_name( - new_attrs.get("__module__", name.lower()) + filename = "{0}.proto".format( + new_attrs.get("__module__", name.lower()).replace(".", "/") ) # Get or create the information about the file, including the # descriptor to which the new message descriptor shall be added. - file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) + file_info = _file_info._FileInfo.registry.setdefault( + filename, + _file_info._FileInfo( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, package=package, syntax="proto3", + ), + enums=collections.OrderedDict(), + messages=collections.OrderedDict(), + name=filename, + nested={}, + ), + ) # Ensure any imports that would be necessary are assigned to the file # descriptor proto being created. @@ -283,13 +286,7 @@ def pb(cls, obj=None, *, coerce: bool = False): if coerce: obj = cls(obj) else: - raise TypeError( - "%r is not an instance of %s" - % ( - obj, - cls.__name__, - ) - ) + raise TypeError("%r is not an instance of %s" % (obj, cls.__name__,)) return obj._pb def wrap(cls, pb): @@ -328,24 +325,17 @@ def deserialize(cls, payload: bytes) -> "Message": """ return cls.wrap(cls.pb().FromString(payload)) - def to_json(cls, instance, *, use_integers_for_enums=True) -> str: + def to_json(cls, instance) -> str: """Given a message instance, serialize it to json Args: instance: An instance of this message type, or something 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. 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=True, - ) + return MessageToJson(cls.pb(instance)) def from_json(cls, payload) -> "Message": """Given a json string representing an instance, @@ -409,10 +399,7 @@ def __init__(self, mapping=None, **kwargs): # Sanity check: Did we get something not a map? Error if so. raise TypeError( "Invalid constructor input for %s: %r" - % ( - self.__class__.__name__, - mapping, - ) + % (self.__class__.__name__, mapping,) ) else: # Can't have side effects on mapping. diff --git a/packages/proto-plus/proto/modules.py b/packages/proto-plus/proto/modules.py index 7c752d6971db..224213f40d6c 100644 --- a/packages/proto-plus/proto/modules.py +++ b/packages/proto-plus/proto/modules.py @@ -17,8 +17,7 @@ _ProtoModule = collections.namedtuple( - "ProtoModule", - ["package", "marshal", "manifest"], + "ProtoModule", ["package", "marshal", "manifest"], ) @@ -40,11 +39,7 @@ def define_module( """ if not marshal: marshal = package - return _ProtoModule( - package=package, - marshal=marshal, - manifest=frozenset(manifest), - ) + return _ProtoModule(package=package, marshal=marshal, manifest=frozenset(manifest),) __all__ = ("define_module",) diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index ad6e899fdc8f..2cf373527c00 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -85,9 +85,7 @@ def _register_messages(scope, iterable, sym_db): """Create and register messages from the file descriptor.""" for name, descriptor in iterable.items(): new_msg = reflection.GeneratedProtocolMessageType( - name, - (message.Message,), - {"DESCRIPTOR": descriptor, "__module__": None}, + name, (message.Message,), {"DESCRIPTOR": descriptor, "__module__": None}, ) sym_db.RegisterMessage(new_msg) setattr(scope, name, new_msg) diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index 1471abdd1d0c..b494bd0c9adc 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -252,16 +252,3 @@ class Foo(proto.Message): assert "color" not in foo assert not foo.color assert Foo.pb(foo).color == 0 - - -class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - ABYSSOPELAGIC = 2 - HADOPELAGIC = 3 - - -def test_enum_outest(): - z = Zone(value=Zone.MESOPELAGIC) - - assert z == Zone.MESOPELAGIC diff --git a/packages/proto-plus/tests/test_fields_map_composite.py b/packages/proto-plus/tests/test_fields_map_composite.py index 3b2d8fd9acd6..13bf0c5f6bf2 100644 --- a/packages/proto-plus/tests/test_fields_map_composite.py +++ b/packages/proto-plus/tests/test_fields_map_composite.py @@ -22,12 +22,7 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField( - proto.STRING, - proto.MESSAGE, - number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz(foos={"i": Foo(bar=42), "j": Foo(bar=24)}) assert len(baz.foos) == 2 @@ -41,12 +36,7 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField( - proto.STRING, - proto.MESSAGE, - number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz(foos={"i": {"bar": 42}, "j": {"bar": 24}}) assert len(baz.foos) == 2 @@ -62,12 +52,7 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField( - proto.STRING, - proto.MESSAGE, - number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz() baz.foos["i"] = Foo(bar=42) @@ -83,12 +68,7 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField( - proto.STRING, - proto.MESSAGE, - number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz() baz.foos["i"] = Foo() @@ -102,12 +82,7 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField( - proto.STRING, - proto.MESSAGE, - number=1, - message=Foo, - ) + foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) baz = Baz() baz.foos["i"] = Foo(bar=42) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index d64675200815..2990eea6597b 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -74,9 +74,7 @@ class Baz(proto.Message): def test_repeated_composite_marshaled(): class Foo(proto.Message): timestamps = proto.RepeatedField( - proto.MESSAGE, - message=timestamp_pb2.Timestamp, - number=1, + proto.MESSAGE, message=timestamp_pb2.Timestamp, number=1, ) foo = Foo( diff --git a/packages/proto-plus/tests/test_file_info_salting.py b/packages/proto-plus/tests/test_file_info_salting.py index 15afdc08d70e..e87731cfbe39 100644 --- a/packages/proto-plus/tests/test_file_info_salting.py +++ b/packages/proto-plus/tests/test_file_info_salting.py @@ -34,9 +34,7 @@ def sample_file_info(name): filename, _file_info._FileInfo( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, - package=package, - syntax="proto3", + name=filename, package=package, syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), diff --git a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py index 43380aa6197c..0a0e7c579e99 100644 --- a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py +++ b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py @@ -21,10 +21,7 @@ from proto import _file_info, _package_info PACKAGE = "a.test.package.salting.with.manifest" -__protobuf__ = proto.module( - package=PACKAGE, - manifest={"This", "That"}, -) +__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) class This(proto.Message): @@ -52,9 +49,7 @@ def sample_file_info(name): filename, _file_info._FileInfo( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, - package=package, - syntax="proto3", + name=filename, package=package, syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index b4ffcf17e3fb..a300a84b8f26 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -50,41 +50,3 @@ class Squid(proto.Message): s2 = Squid.from_json(json) assert s == s2 - - -def test_stringy_enums(): - class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 - - class Squid(proto.Message): - zone = proto.Field(Zone, number=1) - - s1 = Squid(zone=Zone.MESOPELAGIC) - json = Squid.to_json(s1, use_integers_for_enums=False) - - assert "MESOPELAGIC" in json - - s2 = Squid.from_json(json) - assert s2.zone == s1.zone - - -def test_default_enums(): - class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 - - class Squid(proto.Message): - zone = proto.Field(Zone, number=1) - - s = Squid() - assert s.zone == Zone.EPIPELAGIC - json1 = Squid.to_json(s).replace(" ", "").replace("\n", "") - assert json1 == '{"zone":0}' - - json2 = Squid.to_json(s, use_integers_for_enums=False) - assert "EPIPELAGIC" in json2 diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 5fe09ab5c303..631858935226 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -28,9 +28,7 @@ def test_timestamp_read(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=timestamp_pb2.Timestamp(seconds=1335020400)) @@ -47,9 +45,7 @@ class Foo(proto.Message): def test_timestamp_write_init(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) @@ -64,9 +60,7 @@ class Foo(proto.Message): def test_timestamp_write(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -83,9 +77,7 @@ class Foo(proto.Message): def test_timestamp_write_pb2(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -101,9 +93,7 @@ class Foo(proto.Message): def test_timestamp_rmw_nanos(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -120,9 +110,7 @@ class Foo(proto.Message): def test_timestamp_absence(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -132,9 +120,7 @@ class Foo(proto.Message): def test_timestamp_del(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, - number=1, - message=timestamp_pb2.Timestamp, + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) @@ -144,11 +130,7 @@ class Foo(proto.Message): def test_duration_read(): class Foo(proto.Message): - ttl = proto.Field( - proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) foo = Foo(ttl=duration_pb2.Duration(seconds=60, nanos=1000)) assert isinstance(foo.ttl, timedelta) @@ -160,11 +142,7 @@ class Foo(proto.Message): def test_duration_write_init(): class Foo(proto.Message): - ttl = proto.Field( - proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) foo = Foo(ttl=timedelta(days=2)) assert isinstance(foo.ttl, timedelta) @@ -177,11 +155,7 @@ class Foo(proto.Message): def test_duration_write(): class Foo(proto.Message): - ttl = proto.Field( - proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) foo = Foo() foo.ttl = timedelta(seconds=120) @@ -193,11 +167,7 @@ class Foo(proto.Message): def test_duration_write_pb2(): class Foo(proto.Message): - ttl = proto.Field( - proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) foo = Foo() foo.ttl = duration_pb2.Duration(seconds=120) @@ -209,11 +179,7 @@ class Foo(proto.Message): def test_duration_del(): class Foo(proto.Message): - ttl = proto.Field( - proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) foo = Foo(ttl=timedelta(seconds=900)) del foo.ttl @@ -225,11 +191,7 @@ class Foo(proto.Message): def test_duration_nanos_rmw(): class Foo(proto.Message): - ttl = proto.Field( - proto.MESSAGE, - number=1, - message=duration_pb2.Duration, - ) + ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) foo = Foo(ttl=timedelta(microseconds=50)) assert foo.ttl.microseconds == 50 diff --git a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py index e27caf990dcb..ffe206a696f9 100644 --- a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py +++ b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py @@ -20,11 +20,7 @@ def test_bool_value_init(): class Foo(proto.Message): - bar = proto.Field( - proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) assert Foo(bar=True).bar is True assert Foo(bar=False).bar is False @@ -33,11 +29,7 @@ class Foo(proto.Message): def test_bool_value_init_dict(): class Foo(proto.Message): - bar = proto.Field( - proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) assert Foo({"bar": True}).bar is True assert Foo({"bar": False}).bar is False @@ -46,11 +38,7 @@ class Foo(proto.Message): def test_bool_value_distinction_from_bool(): class Foo(proto.Message): - bar = proto.Field( - proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) baz = proto.Field(proto.BOOL, number=2) assert Foo().bar is None @@ -75,11 +63,7 @@ class Foo(proto.Message): def test_bool_value_write_bool_value(): class Foo(proto.Message): - bar = proto.Field( - proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) foo = Foo(bar=True) foo.bar = wrappers_pb2.BoolValue() @@ -88,11 +72,7 @@ class Foo(proto.Message): def test_bool_value_del(): class Foo(proto.Message): - bar = proto.Field( - proto.MESSAGE, - message=wrappers_pb2.BoolValue, - number=1, - ) + bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) foo = Foo(bar=False) assert foo.bar is False diff --git a/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py index e67e8472b830..32c2bc15ecaf 100644 --- a/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py +++ b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py @@ -16,10 +16,7 @@ PACKAGE = "a.test.package.with.and.without.manifest" -__protobuf__ = proto.module( - package=PACKAGE, - manifest={"This", "That"}, -) +__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) class This(proto.Message): diff --git a/packages/proto-plus/tests/test_message_filename_with_manifest.py b/packages/proto-plus/tests/test_message_filename_with_manifest.py index aa96028ef792..e12e5a511b6f 100644 --- a/packages/proto-plus/tests/test_message_filename_with_manifest.py +++ b/packages/proto-plus/tests/test_message_filename_with_manifest.py @@ -15,10 +15,7 @@ import proto PACKAGE = "a.test.package.with.manifest" -__protobuf__ = proto.module( - package=PACKAGE, - manifest={"ThisFoo", "ThisBar"}, -) +__protobuf__ = proto.module(package=PACKAGE, manifest={"ThisFoo", "ThisBar"},) class ThisFoo(proto.Message): diff --git a/packages/proto-plus/tests/test_message_nested.py b/packages/proto-plus/tests/test_message_nested.py index 41af9507f22e..89dfad68610d 100644 --- a/packages/proto-plus/tests/test_message_nested.py +++ b/packages/proto-plus/tests/test_message_nested.py @@ -56,8 +56,7 @@ class Bacon(proto.Message): baz = proto.Field(proto.MESSAGE, number=2, message=Baz) foo = Foo( - bar={"spam": "xyz", "eggs": False}, - baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), + bar={"spam": "xyz", "eggs": False}, baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), ) assert foo.bar.spam == "xyz" assert not foo.bar.eggs diff --git a/packages/proto-plus/tests/test_modules.py b/packages/proto-plus/tests/test_modules.py index 79a99da1fb00..897ce46cf075 100644 --- a/packages/proto-plus/tests/test_modules.py +++ b/packages/proto-plus/tests/test_modules.py @@ -38,8 +38,7 @@ class Foo(proto.Message): def test_module_package_explicit_marshal(): sys.modules[__name__].__protobuf__ = proto.module( - package="spam.eggs.v1", - marshal="foo", + package="spam.eggs.v1", marshal="foo", ) try: @@ -55,10 +54,7 @@ class Foo(proto.Message): def test_module_manifest(): - __protobuf__ = proto.module( - manifest={"Foo", "Bar", "Baz"}, - package="spam.eggs.v1", - ) + __protobuf__ = proto.module(manifest={"Foo", "Bar", "Baz"}, package="spam.eggs.v1",) # We want to fake a module, but modules have attribute access, and # `frame.f_locals` is a dictionary. Since we only actually care about From e1efc8d8702fe96becd247b5f1bf1487547ae7d2 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2020 16:29:11 -0600 Subject: [PATCH 067/272] chore: release 1.8.1 (#111) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 20dd2f04399a..13cfdbce9f97 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.8.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.8.0...v1.8.1) (2020-08-28) + + +### Bug Fixes + +* revert "feat: allow enum strings in json serialization and deserialization" ([#110](https://www.github.com/googleapis/proto-plus-python/issues/110)) ([bd3d50e](https://www.github.com/googleapis/proto-plus-python/commit/bd3d50e6b4d4574a21592f51adf7b248ededd545)), closes [#107](https://www.github.com/googleapis/proto-plus-python/issues/107) + ## [1.8.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.7.1...v1.8.0) (2020-08-28) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 51ce0c643d6f..c0930051106e 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.8.0" +version = "1.8.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 1ee8e4c5b684cac0de6c8de625fc7148fdf2f725 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 2 Sep 2020 16:34:45 -0700 Subject: [PATCH 068/272] feat: json serialization and deserialization support stringy enums (#112) For protobuf messages that contain enum fields, it is now possible to specify that enum variants should be serialized as names and not as integers. E.g. json_str = MyMessage.to_json(my_message, enum_strings=True) Similarly, serialization from json that uses this convention is now supported. This is useful for interoperation with other data sources that do use strings to define enum variants in json serialization; and for debugging, where visually inspecting data structures can be helpful, and variant names are more informative than numerical values. Re-addition of #107 with correct support for nested enum definitions this time. Includes new tests and modifications to existing tests that take into account the fact that Enums now share many of the same properties as messages: their definition adds an entry to the file descriptor proto, out-of-order field references to enums require resolution, and out-of-file references require proto-file dependency definition. --- packages/proto-plus/noxfile.py | 15 +- packages/proto-plus/proto/_file_info.py | 44 +++++- packages/proto-plus/proto/enums.py | 55 +++++++ packages/proto-plus/proto/fields.py | 25 ++- packages/proto-plus/proto/message.py | 42 ++--- packages/proto-plus/tests/clam.py | 29 ++++ packages/proto-plus/tests/mollusc.py | 22 +++ packages/proto-plus/tests/test_fields_enum.py | 144 +++++++++++++----- .../tests/test_file_info_salting.py | 1 + .../test_file_info_salting_with_manifest.py | 1 + packages/proto-plus/tests/test_json.py | 45 ++++++ .../tests/test_marshal_types_enum.py | 2 + packages/proto-plus/tests/zone.py | 26 ++++ 13 files changed, 373 insertions(+), 78 deletions(-) create mode 100644 packages/proto-plus/tests/clam.py create mode 100644 packages/proto-plus/tests/mollusc.py create mode 100644 packages/proto-plus/tests/zone.py diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 0e7078355c8a..94ce2c31e706 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -30,11 +30,16 @@ def unit(session, proto="python"): "py.test", "-W=error", "--quiet", - "--cov=proto", - "--cov-config=.coveragerc", - "--cov-report=term", - "--cov-report=html", - os.path.join("tests", ""), + *( + session.posargs # Coverage info when running individual tests is annoying. + or [ + "--cov=proto", + "--cov-config=.coveragerc", + "--cov-report=term", + "--cov-report=html", + os.path.join("tests", ""), + ] + ), ) diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 0c1e74f00d8d..34ec79c5d719 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import collections.abc +import collections import inspect import logging +from google.protobuf import descriptor_pb2 from google.protobuf import descriptor_pool from google.protobuf import message from google.protobuf import reflection @@ -27,11 +28,33 @@ class _FileInfo( collections.namedtuple( - "_FileInfo", ["descriptor", "messages", "enums", "name", "nested"] + "_FileInfo", + ["descriptor", "messages", "enums", "name", "nested", "nested_enum"], ) ): registry = {} # Mapping[str, '_FileInfo'] + @classmethod + def maybe_add_descriptor(cls, filename, package): + descriptor = cls.registry.get(filename) + if not descriptor: + descriptor = cls.registry[filename] = cls( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, package=package, syntax="proto3", + ), + enums=collections.OrderedDict(), + messages=collections.OrderedDict(), + name=filename, + nested={}, + nested_enum={}, + ) + + return descriptor + + @staticmethod + def proto_file_name(name): + return "{0}.proto".format(name.replace(".", "/")) + def _get_manifest(self, new_class): module = inspect.getmodule(new_class) if hasattr(module, "__protobuf__"): @@ -107,6 +130,13 @@ def generate_file_pb(self, new_class, fallback_salt=""): for field in proto_plus_message._meta.fields.values(): if field.message and isinstance(field.message, str): field.message = self.messages[field.message] + elif field.enum and isinstance(field.enum, str): + field.enum = self.enums[field.enum] + + # Same thing for enums + for full_name, proto_plus_enum in self.enums.items(): + descriptor = pool.FindEnumTypeByName(full_name) + proto_plus_enum._meta.pb = descriptor # We no longer need to track this file's info; remove it from # the module's registry and from this object. @@ -130,14 +160,16 @@ def ready(self, new_class): """ # If there are any nested descriptors that have not been assigned to # the descriptors that should contain them, then we are not ready. - if len(self.nested): + if len(self.nested) or len(self.nested_enum): return False # If there are any unresolved fields (fields with a composite message # declared as a string), ensure that the corresponding message is # declared. for field in self.unresolved_fields: - if field.message not in self.messages: + if (field.message and field.message not in self.messages) or ( + field.enum and field.enum not in self.enums + ): return False # If the module in which this class is defined provides a @@ -156,5 +188,7 @@ def unresolved_fields(self): """Return fields with referencing message types as strings.""" for proto_plus_message in self.messages.values(): for field in proto_plus_message._meta.fields.values(): - if field.message and isinstance(field.message, str): + if (field.message and isinstance(field.message, str)) or ( + field.enum and isinstance(field.enum, str) + ): yield field diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index ef1af5912a1d..22e8f42debb6 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -14,6 +14,9 @@ import enum +from google.protobuf import descriptor_pb2 + +from proto import _file_info from proto import _package_info from proto.marshal.rules.enums import EnumRule @@ -30,12 +33,58 @@ def __new__(mcls, name, bases, attrs): # this component belongs within the file. package, marshal = _package_info.compile(name, attrs) + # Determine the local path of this proto component within the file. + local_path = tuple(attrs.get("__qualname__", name).split(".")) + + # Sanity check: We get the wrong full name if a class is declared + # inside a function local scope; correct this. + if "" in local_path: + ix = local_path.index("") + local_path = local_path[: ix - 1] + local_path[ix + 1 :] + + # Determine the full name in protocol buffers. + full_name = ".".join((package,) + local_path).lstrip(".") + filename = _file_info._FileInfo.proto_file_name( + attrs.get("__module__", name.lower()) + ) + enum_desc = descriptor_pb2.EnumDescriptorProto( + name=name, + # Note: the superclass ctor removes the variants, so get them now. + # Note: proto3 requires that the first variant value be zero. + value=sorted( + ( + descriptor_pb2.EnumValueDescriptorProto(name=name, number=number) + # Minor hack to get all the enum variants out. + for name, number in attrs.items() + if isinstance(number, int) + ), + key=lambda v: v.number, + ), + ) + + file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) + if len(local_path) == 1: + file_info.descriptor.enum_type.add().MergeFrom(enum_desc) + else: + file_info.nested_enum[local_path] = enum_desc + # Run the superclass constructor. cls = super().__new__(mcls, name, bases, attrs) + # We can't just add a "_meta" element to attrs because the Enum + # machinery doesn't know what to do with a non-int value. + # The pb is set later, in generate_file_pb + cls._meta = _EnumInfo(full_name=full_name, pb=None) + + file_info.enums[full_name] = cls + # Register the enum with the marshal. marshal.register(cls, EnumRule(cls)) + # Generate the descriptor for the file if it is ready. + if file_info.ready(new_class=cls): + file_info.generate_file_pb(new_class=cls, fallback_salt=full_name) + # Done; return the class. return cls @@ -44,3 +93,9 @@ class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): """A enum object that also builds a protobuf enum descriptor.""" pass + + +class _EnumInfo: + def __init__(self, *, full_name: str, pb): + self.full_name = full_name + self.pb = pb diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index c951a88c4708..f580ef2f2ff0 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -72,7 +72,6 @@ def __init__( def descriptor(self): """Return the descriptor for the field.""" if not self._descriptor: - proto_type = self.proto_type # Resolve the message type, if any, to a string. type_name = None if isinstance(self.message, str): @@ -85,29 +84,23 @@ def descriptor(self): type_name = ( self.message.DESCRIPTOR.full_name if hasattr(self.message, "DESCRIPTOR") - else self.message.meta.full_name + else self.message._meta.full_name ) + elif isinstance(self.enum, str): + if not self.enum.startswith(self.package): + self.enum = "{package}.{name}".format( + package=self.package, name=self.enum, + ) + type_name = self.enum elif self.enum: - # Nos decipiat. - # - # As far as the wire format is concerned, enums are int32s. - # Protocol buffers itself also only sends ints; the enum - # objects are simply helper classes for translating names - # and values and it is the user's job to resolve to an int. - # - # Therefore, the non-trivial effort of adding the actual - # enum descriptors seems to add little or no actual value. - # - # FIXME: Eventually, come back and put in the actual enum - # descriptors. - proto_type = ProtoType.INT32 + type_name = self.enum._meta.full_name # Set the descriptor. self._descriptor = descriptor_pb2.FieldDescriptorProto( name=self.name, number=self.number, label=3 if self.repeated else 1, - type=proto_type, + type=self.proto_type, type_name=type_name, json_name=self.json_name, proto3_optional=self.optional, diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 4873126ca4ee..8946ea8f4a58 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -145,7 +145,6 @@ def __new__(mcls, name, bases, attrs): field_msg = field.message if hasattr(field_msg, "pb") and callable(field_msg.pb): field_msg = field_msg.pb() - # Sanity check: The field's message may not yet be defined if # it was a Message defined in the same file, and the file # descriptor proto has not yet been generated. @@ -154,7 +153,13 @@ def __new__(mcls, name, bases, attrs): # correctly when the file descriptor is created later. if field_msg: proto_imports.add(field_msg.DESCRIPTOR.file.name) - symbol_database.Default().RegisterMessage(field_msg) + + # Same thing, but for enums. + elif field.enum and not isinstance(field.enum, str): + field_enum = field.enum._meta.pb + + if field_enum: + proto_imports.add(field_enum.file.name) # Increment the field index counter. index += 1 @@ -183,24 +188,13 @@ def __new__(mcls, name, bases, attrs): # Determine the filename. # We determine an appropriate proto filename based on the # Python module. - filename = "{0}.proto".format( - new_attrs.get("__module__", name.lower()).replace(".", "/") + filename = _file_info._FileInfo.proto_file_name( + new_attrs.get("__module__", name.lower()) ) # Get or create the information about the file, including the # descriptor to which the new message descriptor shall be added. - file_info = _file_info._FileInfo.registry.setdefault( - filename, - _file_info._FileInfo( - descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", - ), - enums=collections.OrderedDict(), - messages=collections.OrderedDict(), - name=filename, - nested={}, - ), - ) + file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) # Ensure any imports that would be necessary are assigned to the file # descriptor proto being created. @@ -227,6 +221,11 @@ def __new__(mcls, name, bases, attrs): for child_path in child_paths: desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path)) + # Same thing, but for enums + child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]] + for child_path in child_paths: + desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path)) + # Add the descriptor to the file if it is a top-level descriptor, # or to a "holding area" for nested messages otherwise. if len(local_path) == 1: @@ -325,17 +324,24 @@ def deserialize(cls, payload: bytes) -> "Message": """ return cls.wrap(cls.pb().FromString(payload)) - def to_json(cls, instance) -> str: + def to_json(cls, instance, *, use_integers_for_enums=True) -> str: """Given a message instance, serialize it to json Args: instance: An instance of this message type, or something 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. Returns: str: The json string representation of the protocol buffer. """ - return MessageToJson(cls.pb(instance)) + return MessageToJson( + cls.pb(instance), + use_integers_for_enums=use_integers_for_enums, + including_default_value_fields=True, + ) def from_json(cls, payload) -> "Message": """Given a json string representing an instance, diff --git a/packages/proto-plus/tests/clam.py b/packages/proto-plus/tests/clam.py new file mode 100644 index 000000000000..a4255fe8083d --- /dev/null +++ b/packages/proto-plus/tests/clam.py @@ -0,0 +1,29 @@ +# Copyright (C) 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + +__protobuf__ = proto.module(package="ocean.clam.v1", manifest={"Clam", "Species",},) + + +class Species(proto.Enum): + UNKNOWN = 0 + SQUAMOSA = 1 + DURASA = 2 + GIGAS = 3 + + +class Clam(proto.Message): + species = proto.Field(proto.ENUM, number=1, enum="Species") + mass_kg = proto.Field(proto.DOUBLE, number=2) diff --git a/packages/proto-plus/tests/mollusc.py b/packages/proto-plus/tests/mollusc.py new file mode 100644 index 000000000000..d12e4cb9b1b0 --- /dev/null +++ b/packages/proto-plus/tests/mollusc.py @@ -0,0 +1,22 @@ +# Copyright (C) 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto +import zone + +__protobuf__ = proto.module(package="ocean.mollusc.v1", manifest={"Mollusc",},) + + +class Mollusc(proto.Message): + zone = proto.Field(zone.Zone, number=1) diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index b494bd0c9adc..a73de1316118 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -13,18 +13,19 @@ # limitations under the License. import proto +import sys def test_outer_enum_init(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color=Color.RED) assert foo.color == Color.RED assert foo.color == 1 @@ -34,15 +35,15 @@ class Foo(proto.Message): def test_outer_enum_init_int(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color=1) assert foo.color == Color.RED assert foo.color == 1 @@ -52,15 +53,15 @@ class Foo(proto.Message): def test_outer_enum_init_str(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color="RED") assert foo.color == Color.RED assert foo.color == 1 @@ -70,15 +71,15 @@ class Foo(proto.Message): def test_outer_enum_init_dict(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo({"color": 1}) assert foo.color == Color.RED assert foo.color == 1 @@ -88,15 +89,15 @@ class Foo(proto.Message): def test_outer_enum_init_dict_str(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo({"color": "BLUE"}) assert foo.color == Color.BLUE assert foo.color == 3 @@ -106,15 +107,15 @@ class Foo(proto.Message): def test_outer_enum_init_pb2(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo(Foo.pb()(color=Color.RED)) assert foo.color == Color.RED assert foo.color == 1 @@ -124,15 +125,15 @@ class Foo(proto.Message): def test_outer_enum_unset(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo() assert foo.color == Color.COLOR_UNSPECIFIED assert foo.color == 0 @@ -143,15 +144,15 @@ class Foo(proto.Message): def test_outer_enum_write(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo() foo.color = Color.GREEN assert foo.color == Color.GREEN @@ -161,15 +162,15 @@ class Foo(proto.Message): def test_outer_enum_write_int(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo() foo.color = 3 assert foo.color == Color.BLUE @@ -180,15 +181,15 @@ class Foo(proto.Message): def test_outer_enum_write_str(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo() foo.color = "BLUE" assert foo.color == Color.BLUE @@ -206,7 +207,7 @@ class Color(proto.Enum): GREEN = 2 BLUE = 3 - color = proto.Field(proto.ENUM, number=1, enum=Color) + color = proto.Field(Color, number=1) foo = Foo(color=Foo.Color.RED) assert foo.color == Foo.Color.RED @@ -235,15 +236,15 @@ class Color(proto.Enum): def test_enum_del(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color=Color.BLUE) del foo.color assert foo.color == Color.COLOR_UNSPECIFIED @@ -252,3 +253,78 @@ class Foo(proto.Message): assert "color" not in foo assert not foo.color assert Foo.pb(foo).color == 0 + + +def test_nested_enum_from_string(): + class Trawl(proto.Message): + # Note: this indirection with the nested field + # is necessary to trigger the exception for testing. + # Setting the field in an existing message accepts strings AND + # checks for valid variants. + # Similarly, constructing a message directly with a top level + # enum field kwarg passed as a string is also handled correctly, i.e. + # s = Squid(zone="ABYSSOPELAGIC") + # does NOT raise an exception. + squids = proto.RepeatedField("Squid", number=1) + + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + t = Trawl(squids=[{"zone": "MESOPELAGIC"}]) + assert t.squids[0] == Squid(zone=Zone.MESOPELAGIC) + + +def test_enum_field_by_string(): + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + s = Squid(zone=Zone.BATHYPELAGIC) + assert s.zone == Zone.BATHYPELAGIC + + +def test_enum_field_by_string_with_package(): + sys.modules[__name__].__protobuf__ = proto.module(package="mollusca.cephalopoda") + try: + + class Octopus(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="mollusca.cephalopoda.Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + finally: + del sys.modules[__name__].__protobuf__ + + o = Octopus(zone="MESOPELAGIC") + assert o.zone == Zone.MESOPELAGIC + + +def test_enums_in_different_files(): + import mollusc + import zone + + m = mollusc.Mollusc(zone="BATHYPELAGIC") + + assert m.zone == zone.Zone.BATHYPELAGIC + + +def test_enums_in_one_file(): + import clam + + c = clam.Clam(species=clam.Species.DURASA) + assert c.species == clam.Species.DURASA diff --git a/packages/proto-plus/tests/test_file_info_salting.py b/packages/proto-plus/tests/test_file_info_salting.py index e87731cfbe39..c7a91d93a600 100644 --- a/packages/proto-plus/tests/test_file_info_salting.py +++ b/packages/proto-plus/tests/test_file_info_salting.py @@ -40,6 +40,7 @@ def sample_file_info(name): messages=collections.OrderedDict(), name=filename, nested={}, + nested_enum={}, ), ) diff --git a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py index 0a0e7c579e99..1fc5046226c4 100644 --- a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py +++ b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py @@ -55,6 +55,7 @@ def sample_file_info(name): messages=collections.OrderedDict(), name=filename, nested={}, + nested_enum={}, ), ) diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index a300a84b8f26..58195e89f311 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -50,3 +50,48 @@ class Squid(proto.Message): s2 = Squid.from_json(json) assert s == s2 + + +def test_json_stringy_enums(): + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + s1 = Squid(zone=Zone.MESOPELAGIC) + json = ( + Squid.to_json(s1, use_integers_for_enums=False) + .replace(" ", "") + .replace("\n", "") + ) + assert json == '{"zone":"MESOPELAGIC"}' + + s2 = Squid.from_json(json) + assert s2.zone == s1.zone + + +def test_json_default_enums(): + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + s = Squid() + assert s.zone == Zone.EPIPELAGIC + json1 = Squid.to_json(s).replace(" ", "").replace("\n", "") + assert json1 == '{"zone":0}' + + json2 = ( + Squid.to_json(s, use_integers_for_enums=False) + .replace(" ", "") + .replace("\n", "") + ) + assert json2 == '{"zone":"EPIPELAGIC"}' diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py index 129f70161607..1d302053b3f5 100644 --- a/packages/proto-plus/tests/test_marshal_types_enum.py +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -18,6 +18,8 @@ import proto from proto.marshal.rules.enums import EnumRule +__protobuf__ = proto.module(package="test.marshal.enum") + def test_to_proto(): class Foo(proto.Enum): diff --git a/packages/proto-plus/tests/zone.py b/packages/proto-plus/tests/zone.py new file mode 100644 index 000000000000..4e7ef4023bb4 --- /dev/null +++ b/packages/proto-plus/tests/zone.py @@ -0,0 +1,26 @@ +# Copyright (C) 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import proto + + +__protobuf__ = proto.module(package="ocean.zone.v1", manifest={"Zone",},) + + +class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 From de20cac7efc2878130a91f97c0a4505fe56ee4c5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 16:37:36 -0700 Subject: [PATCH 069/272] chore: release 1.9.0 (#114) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 13cfdbce9f97..578ad669b408 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.9.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.8.1...v1.9.0) (2020-09-02) + + +### Features + +* json serialization and deserialization support stringy enums ([#112](https://www.github.com/googleapis/proto-plus-python/issues/112)) ([8d2e3a3](https://www.github.com/googleapis/proto-plus-python/commit/8d2e3a3439650dab9ca7c6ff49ed067838a02a45)), closes [#107](https://www.github.com/googleapis/proto-plus-python/issues/107) + ### [1.8.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.8.0...v1.8.1) (2020-08-28) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index c0930051106e..37b6be3da16d 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.8.1" +version = "1.9.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 7bf6dce5464a7eedc390537ca789b71f13c64313 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 3 Sep 2020 15:01:34 -0600 Subject: [PATCH 070/272] Revert "feat: json serialization and deserialization support stringy enums (#112)" (#116) This reverts commit 8d2e3a3439650dab9ca7c6ff49ed067838a02a45. --- packages/proto-plus/noxfile.py | 15 +- packages/proto-plus/proto/_file_info.py | 44 +----- packages/proto-plus/proto/enums.py | 55 ------- packages/proto-plus/proto/fields.py | 25 +-- packages/proto-plus/proto/message.py | 42 +++-- packages/proto-plus/tests/clam.py | 29 ---- packages/proto-plus/tests/mollusc.py | 22 --- packages/proto-plus/tests/test_fields_enum.py | 144 +++++------------- .../tests/test_file_info_salting.py | 1 - .../test_file_info_salting_with_manifest.py | 1 - packages/proto-plus/tests/test_json.py | 45 ------ .../tests/test_marshal_types_enum.py | 2 - packages/proto-plus/tests/zone.py | 26 ---- 13 files changed, 78 insertions(+), 373 deletions(-) delete mode 100644 packages/proto-plus/tests/clam.py delete mode 100644 packages/proto-plus/tests/mollusc.py delete mode 100644 packages/proto-plus/tests/zone.py diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 94ce2c31e706..0e7078355c8a 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -30,16 +30,11 @@ def unit(session, proto="python"): "py.test", "-W=error", "--quiet", - *( - session.posargs # Coverage info when running individual tests is annoying. - or [ - "--cov=proto", - "--cov-config=.coveragerc", - "--cov-report=term", - "--cov-report=html", - os.path.join("tests", ""), - ] - ), + "--cov=proto", + "--cov-config=.coveragerc", + "--cov-report=term", + "--cov-report=html", + os.path.join("tests", ""), ) diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 34ec79c5d719..0c1e74f00d8d 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -12,11 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import collections +import collections.abc import inspect import logging -from google.protobuf import descriptor_pb2 from google.protobuf import descriptor_pool from google.protobuf import message from google.protobuf import reflection @@ -28,33 +27,11 @@ class _FileInfo( collections.namedtuple( - "_FileInfo", - ["descriptor", "messages", "enums", "name", "nested", "nested_enum"], + "_FileInfo", ["descriptor", "messages", "enums", "name", "nested"] ) ): registry = {} # Mapping[str, '_FileInfo'] - @classmethod - def maybe_add_descriptor(cls, filename, package): - descriptor = cls.registry.get(filename) - if not descriptor: - descriptor = cls.registry[filename] = cls( - descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", - ), - enums=collections.OrderedDict(), - messages=collections.OrderedDict(), - name=filename, - nested={}, - nested_enum={}, - ) - - return descriptor - - @staticmethod - def proto_file_name(name): - return "{0}.proto".format(name.replace(".", "/")) - def _get_manifest(self, new_class): module = inspect.getmodule(new_class) if hasattr(module, "__protobuf__"): @@ -130,13 +107,6 @@ def generate_file_pb(self, new_class, fallback_salt=""): for field in proto_plus_message._meta.fields.values(): if field.message and isinstance(field.message, str): field.message = self.messages[field.message] - elif field.enum and isinstance(field.enum, str): - field.enum = self.enums[field.enum] - - # Same thing for enums - for full_name, proto_plus_enum in self.enums.items(): - descriptor = pool.FindEnumTypeByName(full_name) - proto_plus_enum._meta.pb = descriptor # We no longer need to track this file's info; remove it from # the module's registry and from this object. @@ -160,16 +130,14 @@ def ready(self, new_class): """ # If there are any nested descriptors that have not been assigned to # the descriptors that should contain them, then we are not ready. - if len(self.nested) or len(self.nested_enum): + if len(self.nested): return False # If there are any unresolved fields (fields with a composite message # declared as a string), ensure that the corresponding message is # declared. for field in self.unresolved_fields: - if (field.message and field.message not in self.messages) or ( - field.enum and field.enum not in self.enums - ): + if field.message not in self.messages: return False # If the module in which this class is defined provides a @@ -188,7 +156,5 @@ def unresolved_fields(self): """Return fields with referencing message types as strings.""" for proto_plus_message in self.messages.values(): for field in proto_plus_message._meta.fields.values(): - if (field.message and isinstance(field.message, str)) or ( - field.enum and isinstance(field.enum, str) - ): + if field.message and isinstance(field.message, str): yield field diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 22e8f42debb6..ef1af5912a1d 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -14,9 +14,6 @@ import enum -from google.protobuf import descriptor_pb2 - -from proto import _file_info from proto import _package_info from proto.marshal.rules.enums import EnumRule @@ -33,58 +30,12 @@ def __new__(mcls, name, bases, attrs): # this component belongs within the file. package, marshal = _package_info.compile(name, attrs) - # Determine the local path of this proto component within the file. - local_path = tuple(attrs.get("__qualname__", name).split(".")) - - # Sanity check: We get the wrong full name if a class is declared - # inside a function local scope; correct this. - if "" in local_path: - ix = local_path.index("") - local_path = local_path[: ix - 1] + local_path[ix + 1 :] - - # Determine the full name in protocol buffers. - full_name = ".".join((package,) + local_path).lstrip(".") - filename = _file_info._FileInfo.proto_file_name( - attrs.get("__module__", name.lower()) - ) - enum_desc = descriptor_pb2.EnumDescriptorProto( - name=name, - # Note: the superclass ctor removes the variants, so get them now. - # Note: proto3 requires that the first variant value be zero. - value=sorted( - ( - descriptor_pb2.EnumValueDescriptorProto(name=name, number=number) - # Minor hack to get all the enum variants out. - for name, number in attrs.items() - if isinstance(number, int) - ), - key=lambda v: v.number, - ), - ) - - file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) - if len(local_path) == 1: - file_info.descriptor.enum_type.add().MergeFrom(enum_desc) - else: - file_info.nested_enum[local_path] = enum_desc - # Run the superclass constructor. cls = super().__new__(mcls, name, bases, attrs) - # We can't just add a "_meta" element to attrs because the Enum - # machinery doesn't know what to do with a non-int value. - # The pb is set later, in generate_file_pb - cls._meta = _EnumInfo(full_name=full_name, pb=None) - - file_info.enums[full_name] = cls - # Register the enum with the marshal. marshal.register(cls, EnumRule(cls)) - # Generate the descriptor for the file if it is ready. - if file_info.ready(new_class=cls): - file_info.generate_file_pb(new_class=cls, fallback_salt=full_name) - # Done; return the class. return cls @@ -93,9 +44,3 @@ class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): """A enum object that also builds a protobuf enum descriptor.""" pass - - -class _EnumInfo: - def __init__(self, *, full_name: str, pb): - self.full_name = full_name - self.pb = pb diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index f580ef2f2ff0..c951a88c4708 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -72,6 +72,7 @@ def __init__( def descriptor(self): """Return the descriptor for the field.""" if not self._descriptor: + proto_type = self.proto_type # Resolve the message type, if any, to a string. type_name = None if isinstance(self.message, str): @@ -84,23 +85,29 @@ def descriptor(self): type_name = ( self.message.DESCRIPTOR.full_name if hasattr(self.message, "DESCRIPTOR") - else self.message._meta.full_name + else self.message.meta.full_name ) - elif isinstance(self.enum, str): - if not self.enum.startswith(self.package): - self.enum = "{package}.{name}".format( - package=self.package, name=self.enum, - ) - type_name = self.enum elif self.enum: - type_name = self.enum._meta.full_name + # Nos decipiat. + # + # As far as the wire format is concerned, enums are int32s. + # Protocol buffers itself also only sends ints; the enum + # objects are simply helper classes for translating names + # and values and it is the user's job to resolve to an int. + # + # Therefore, the non-trivial effort of adding the actual + # enum descriptors seems to add little or no actual value. + # + # FIXME: Eventually, come back and put in the actual enum + # descriptors. + proto_type = ProtoType.INT32 # Set the descriptor. self._descriptor = descriptor_pb2.FieldDescriptorProto( name=self.name, number=self.number, label=3 if self.repeated else 1, - type=self.proto_type, + type=proto_type, type_name=type_name, json_name=self.json_name, proto3_optional=self.optional, diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 8946ea8f4a58..4873126ca4ee 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -145,6 +145,7 @@ def __new__(mcls, name, bases, attrs): field_msg = field.message if hasattr(field_msg, "pb") and callable(field_msg.pb): field_msg = field_msg.pb() + # Sanity check: The field's message may not yet be defined if # it was a Message defined in the same file, and the file # descriptor proto has not yet been generated. @@ -153,13 +154,7 @@ def __new__(mcls, name, bases, attrs): # correctly when the file descriptor is created later. if field_msg: proto_imports.add(field_msg.DESCRIPTOR.file.name) - - # Same thing, but for enums. - elif field.enum and not isinstance(field.enum, str): - field_enum = field.enum._meta.pb - - if field_enum: - proto_imports.add(field_enum.file.name) + symbol_database.Default().RegisterMessage(field_msg) # Increment the field index counter. index += 1 @@ -188,13 +183,24 @@ def __new__(mcls, name, bases, attrs): # Determine the filename. # We determine an appropriate proto filename based on the # Python module. - filename = _file_info._FileInfo.proto_file_name( - new_attrs.get("__module__", name.lower()) + filename = "{0}.proto".format( + new_attrs.get("__module__", name.lower()).replace(".", "/") ) # Get or create the information about the file, including the # descriptor to which the new message descriptor shall be added. - file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) + file_info = _file_info._FileInfo.registry.setdefault( + filename, + _file_info._FileInfo( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, package=package, syntax="proto3", + ), + enums=collections.OrderedDict(), + messages=collections.OrderedDict(), + name=filename, + nested={}, + ), + ) # Ensure any imports that would be necessary are assigned to the file # descriptor proto being created. @@ -221,11 +227,6 @@ def __new__(mcls, name, bases, attrs): for child_path in child_paths: desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path)) - # Same thing, but for enums - child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]] - for child_path in child_paths: - desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path)) - # Add the descriptor to the file if it is a top-level descriptor, # or to a "holding area" for nested messages otherwise. if len(local_path) == 1: @@ -324,24 +325,17 @@ def deserialize(cls, payload: bytes) -> "Message": """ return cls.wrap(cls.pb().FromString(payload)) - def to_json(cls, instance, *, use_integers_for_enums=True) -> str: + def to_json(cls, instance) -> str: """Given a message instance, serialize it to json Args: instance: An instance of this message type, or something 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. 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=True, - ) + return MessageToJson(cls.pb(instance)) def from_json(cls, payload) -> "Message": """Given a json string representing an instance, diff --git a/packages/proto-plus/tests/clam.py b/packages/proto-plus/tests/clam.py deleted file mode 100644 index a4255fe8083d..000000000000 --- a/packages/proto-plus/tests/clam.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import proto - -__protobuf__ = proto.module(package="ocean.clam.v1", manifest={"Clam", "Species",},) - - -class Species(proto.Enum): - UNKNOWN = 0 - SQUAMOSA = 1 - DURASA = 2 - GIGAS = 3 - - -class Clam(proto.Message): - species = proto.Field(proto.ENUM, number=1, enum="Species") - mass_kg = proto.Field(proto.DOUBLE, number=2) diff --git a/packages/proto-plus/tests/mollusc.py b/packages/proto-plus/tests/mollusc.py deleted file mode 100644 index d12e4cb9b1b0..000000000000 --- a/packages/proto-plus/tests/mollusc.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (C) 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import proto -import zone - -__protobuf__ = proto.module(package="ocean.mollusc.v1", manifest={"Mollusc",},) - - -class Mollusc(proto.Message): - zone = proto.Field(zone.Zone, number=1) diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index a73de1316118..b494bd0c9adc 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -13,19 +13,18 @@ # limitations under the License. import proto -import sys def test_outer_enum_init(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(Color, number=1) + foo = Foo(color=Color.RED) assert foo.color == Color.RED assert foo.color == 1 @@ -35,15 +34,15 @@ class Color(proto.Enum): def test_outer_enum_init_int(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(Color, number=1) + foo = Foo(color=1) assert foo.color == Color.RED assert foo.color == 1 @@ -53,15 +52,15 @@ class Color(proto.Enum): def test_outer_enum_init_str(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(Color, number=1) + foo = Foo(color="RED") assert foo.color == Color.RED assert foo.color == 1 @@ -71,15 +70,15 @@ class Color(proto.Enum): def test_outer_enum_init_dict(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(Color, number=1) + foo = Foo({"color": 1}) assert foo.color == Color.RED assert foo.color == 1 @@ -89,15 +88,15 @@ class Color(proto.Enum): def test_outer_enum_init_dict_str(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(Color, number=1) + foo = Foo({"color": "BLUE"}) assert foo.color == Color.BLUE assert foo.color == 3 @@ -107,15 +106,15 @@ class Color(proto.Enum): def test_outer_enum_init_pb2(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + foo = Foo(Foo.pb()(color=Color.RED)) assert foo.color == Color.RED assert foo.color == 1 @@ -125,15 +124,15 @@ class Color(proto.Enum): def test_outer_enum_unset(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + foo = Foo() assert foo.color == Color.COLOR_UNSPECIFIED assert foo.color == 0 @@ -144,15 +143,15 @@ class Color(proto.Enum): def test_outer_enum_write(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + foo = Foo() foo.color = Color.GREEN assert foo.color == Color.GREEN @@ -162,15 +161,15 @@ class Color(proto.Enum): def test_outer_enum_write_int(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum=Color) + foo = Foo() foo.color = 3 assert foo.color == Color.BLUE @@ -181,15 +180,15 @@ class Color(proto.Enum): def test_outer_enum_write_str(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(Color, number=1) + foo = Foo() foo.color = "BLUE" assert foo.color == Color.BLUE @@ -207,7 +206,7 @@ class Color(proto.Enum): GREEN = 2 BLUE = 3 - color = proto.Field(Color, number=1) + color = proto.Field(proto.ENUM, number=1, enum=Color) foo = Foo(color=Foo.Color.RED) assert foo.color == Foo.Color.RED @@ -236,15 +235,15 @@ class Color(proto.Enum): def test_enum_del(): - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum="Color") - class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 + class Foo(proto.Message): + color = proto.Field(Color, number=1) + foo = Foo(color=Color.BLUE) del foo.color assert foo.color == Color.COLOR_UNSPECIFIED @@ -253,78 +252,3 @@ class Color(proto.Enum): assert "color" not in foo assert not foo.color assert Foo.pb(foo).color == 0 - - -def test_nested_enum_from_string(): - class Trawl(proto.Message): - # Note: this indirection with the nested field - # is necessary to trigger the exception for testing. - # Setting the field in an existing message accepts strings AND - # checks for valid variants. - # Similarly, constructing a message directly with a top level - # enum field kwarg passed as a string is also handled correctly, i.e. - # s = Squid(zone="ABYSSOPELAGIC") - # does NOT raise an exception. - squids = proto.RepeatedField("Squid", number=1) - - class Squid(proto.Message): - zone = proto.Field(proto.ENUM, number=1, enum="Zone") - - class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 - - t = Trawl(squids=[{"zone": "MESOPELAGIC"}]) - assert t.squids[0] == Squid(zone=Zone.MESOPELAGIC) - - -def test_enum_field_by_string(): - class Squid(proto.Message): - zone = proto.Field(proto.ENUM, number=1, enum="Zone") - - class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 - - s = Squid(zone=Zone.BATHYPELAGIC) - assert s.zone == Zone.BATHYPELAGIC - - -def test_enum_field_by_string_with_package(): - sys.modules[__name__].__protobuf__ = proto.module(package="mollusca.cephalopoda") - try: - - class Octopus(proto.Message): - zone = proto.Field(proto.ENUM, number=1, enum="mollusca.cephalopoda.Zone") - - class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 - - finally: - del sys.modules[__name__].__protobuf__ - - o = Octopus(zone="MESOPELAGIC") - assert o.zone == Zone.MESOPELAGIC - - -def test_enums_in_different_files(): - import mollusc - import zone - - m = mollusc.Mollusc(zone="BATHYPELAGIC") - - assert m.zone == zone.Zone.BATHYPELAGIC - - -def test_enums_in_one_file(): - import clam - - c = clam.Clam(species=clam.Species.DURASA) - assert c.species == clam.Species.DURASA diff --git a/packages/proto-plus/tests/test_file_info_salting.py b/packages/proto-plus/tests/test_file_info_salting.py index c7a91d93a600..e87731cfbe39 100644 --- a/packages/proto-plus/tests/test_file_info_salting.py +++ b/packages/proto-plus/tests/test_file_info_salting.py @@ -40,7 +40,6 @@ def sample_file_info(name): messages=collections.OrderedDict(), name=filename, nested={}, - nested_enum={}, ), ) diff --git a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py index 1fc5046226c4..0a0e7c579e99 100644 --- a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py +++ b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py @@ -55,7 +55,6 @@ def sample_file_info(name): messages=collections.OrderedDict(), name=filename, nested={}, - nested_enum={}, ), ) diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index 58195e89f311..a300a84b8f26 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -50,48 +50,3 @@ class Squid(proto.Message): s2 = Squid.from_json(json) assert s == s2 - - -def test_json_stringy_enums(): - class Squid(proto.Message): - zone = proto.Field(proto.ENUM, number=1, enum="Zone") - - class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 - - s1 = Squid(zone=Zone.MESOPELAGIC) - json = ( - Squid.to_json(s1, use_integers_for_enums=False) - .replace(" ", "") - .replace("\n", "") - ) - assert json == '{"zone":"MESOPELAGIC"}' - - s2 = Squid.from_json(json) - assert s2.zone == s1.zone - - -def test_json_default_enums(): - class Squid(proto.Message): - zone = proto.Field(proto.ENUM, number=1, enum="Zone") - - class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 - - s = Squid() - assert s.zone == Zone.EPIPELAGIC - json1 = Squid.to_json(s).replace(" ", "").replace("\n", "") - assert json1 == '{"zone":0}' - - json2 = ( - Squid.to_json(s, use_integers_for_enums=False) - .replace(" ", "") - .replace("\n", "") - ) - assert json2 == '{"zone":"EPIPELAGIC"}' diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py index 1d302053b3f5..129f70161607 100644 --- a/packages/proto-plus/tests/test_marshal_types_enum.py +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -18,8 +18,6 @@ import proto from proto.marshal.rules.enums import EnumRule -__protobuf__ = proto.module(package="test.marshal.enum") - def test_to_proto(): class Foo(proto.Enum): diff --git a/packages/proto-plus/tests/zone.py b/packages/proto-plus/tests/zone.py deleted file mode 100644 index 4e7ef4023bb4..000000000000 --- a/packages/proto-plus/tests/zone.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import proto - - -__protobuf__ = proto.module(package="ocean.zone.v1", manifest={"Zone",},) - - -class Zone(proto.Enum): - EPIPELAGIC = 0 - MESOPELAGIC = 1 - BATHYPELAGIC = 2 - ABYSSOPELAGIC = 3 From ef3623212baf5897c7ebad57d8df6f92d7f13b2b Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 3 Sep 2020 17:33:29 -0600 Subject: [PATCH 071/272] chore: add "python 3" classifier (#117) Release-As: 1.9.1 --- packages/proto-plus/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 37b6be3da16d..8bc8bab8065c 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -43,6 +43,7 @@ "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX", + "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", From a56ddddff90d06ecc349db694bedf245a744e703 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 8 Sep 2020 13:20:26 -0600 Subject: [PATCH 072/272] chore: update README (#119) whitespace only change, second attempt at releasing 1.9.1 Release-As: 1.9.1 --- packages/proto-plus/README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 140248c05cb8..228441412958 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -30,3 +30,4 @@ Documentation :target: https://circleci.com/gh/googleapis/proto-plus-python .. |codecov| image:: https://codecov.io/gh/googleapis/proto-plus-python/graph/badge.svg :target: https://codecov.io/gh/googleapis/proto-plus-python + From 2fc261e69e0bd451ab7fcf7367846662e9ad3f41 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 8 Sep 2020 15:16:24 -0600 Subject: [PATCH 073/272] docs: update README (#120) Release-As: 1.9.1 --- packages/proto-plus/README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 228441412958..140248c05cb8 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -30,4 +30,3 @@ Documentation :target: https://circleci.com/gh/googleapis/proto-plus-python .. |codecov| image:: https://codecov.io/gh/googleapis/proto-plus-python/graph/badge.svg :target: https://codecov.io/gh/googleapis/proto-plus-python - From 965e98fed70856abc19b9e1dce7356f963210cf7 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2020 15:20:06 -0600 Subject: [PATCH 074/272] chore: release 1.9.1 (#121) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 12 ++++++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 578ad669b408..d99e60c15537 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +### [1.9.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.0...v1.9.1) (2020-09-08) + + +### Reverts + +* Revert "feat: json serialization and deserialization support stringy enums (#112)" (#116) ([91c6d7b](https://www.github.com/googleapis/proto-plus-python/commit/91c6d7bb27d198439bb323d2454fb94e197bf3dd)), closes [#112](https://www.github.com/googleapis/proto-plus-python/issues/112) [#116](https://www.github.com/googleapis/proto-plus-python/issues/116) + + +### Documentation + +* update README ([#120](https://www.github.com/googleapis/proto-plus-python/issues/120)) ([2077390](https://www.github.com/googleapis/proto-plus-python/commit/2077390d614acb278ab94077f131a895d7184881)) + ## [1.9.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.8.1...v1.9.0) (2020-09-02) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 8bc8bab8065c..2711072c6600 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.9.0" +version = "1.9.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 6983ed2507177a2473b5c01f7a6cd3ab004be766 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 10 Sep 2020 15:48:32 -0700 Subject: [PATCH 075/272] fix: third party enums don't break first class enums (#118) For real, this time. Third party, unwrapped enums no longer cause problems if they are used to describe proto-plus fields. Limitations: * Third party enums cannot be passed by string name to a Field * More scenarios require a module definition than previously * Defining a proto.Enum may cause a FileDescriptor to be added to the descriptor pool. The only way to defer until _all_ Enums and Messages in a module have been added is to provide a manifest or make the Enum a nested type of a Message. This mostly affects unit tests and interactive development. Includes re-addition of stringy enums in JSON. Includes re-addition of fix for #103 Release-As: 1.10.0-dev1 --- packages/proto-plus/noxfile.py | 17 +- packages/proto-plus/proto/_file_info.py | 44 ++++- packages/proto-plus/proto/enums.py | 55 ++++++ packages/proto-plus/proto/fields.py | 32 ++-- packages/proto-plus/proto/message.py | 48 +++-- packages/proto-plus/setup.py | 1 + packages/proto-plus/tests/clam.py | 29 +++ packages/proto-plus/tests/conftest.py | 13 +- packages/proto-plus/tests/mollusc.py | 22 +++ packages/proto-plus/tests/test_fields_enum.py | 169 ++++++++++++++---- .../tests/test_file_info_salting.py | 1 + .../test_file_info_salting_with_manifest.py | 1 + packages/proto-plus/tests/test_json.py | 45 +++++ .../tests/test_marshal_types_enum.py | 2 + packages/proto-plus/tests/zone.py | 26 +++ 15 files changed, 421 insertions(+), 84 deletions(-) create mode 100644 packages/proto-plus/tests/clam.py create mode 100644 packages/proto-plus/tests/mollusc.py create mode 100644 packages/proto-plus/tests/zone.py diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 0e7078355c8a..2f323155d6b2 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -24,17 +24,22 @@ def unit(session, proto="python"): session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto session.install("coverage", "pytest", "pytest-cov", "pytz") - session.install("-e", ".") + session.install("-e", ".[testing]") session.run( "py.test", "-W=error", "--quiet", - "--cov=proto", - "--cov-config=.coveragerc", - "--cov-report=term", - "--cov-report=html", - os.path.join("tests", ""), + *( + session.posargs # Coverage info when running individual tests is annoying. + or [ + "--cov=proto", + "--cov-config=.coveragerc", + "--cov-report=term", + "--cov-report=html", + os.path.join("tests", ""), + ] + ), ) diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 0c1e74f00d8d..34ec79c5d719 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import collections.abc +import collections import inspect import logging +from google.protobuf import descriptor_pb2 from google.protobuf import descriptor_pool from google.protobuf import message from google.protobuf import reflection @@ -27,11 +28,33 @@ class _FileInfo( collections.namedtuple( - "_FileInfo", ["descriptor", "messages", "enums", "name", "nested"] + "_FileInfo", + ["descriptor", "messages", "enums", "name", "nested", "nested_enum"], ) ): registry = {} # Mapping[str, '_FileInfo'] + @classmethod + def maybe_add_descriptor(cls, filename, package): + descriptor = cls.registry.get(filename) + if not descriptor: + descriptor = cls.registry[filename] = cls( + descriptor=descriptor_pb2.FileDescriptorProto( + name=filename, package=package, syntax="proto3", + ), + enums=collections.OrderedDict(), + messages=collections.OrderedDict(), + name=filename, + nested={}, + nested_enum={}, + ) + + return descriptor + + @staticmethod + def proto_file_name(name): + return "{0}.proto".format(name.replace(".", "/")) + def _get_manifest(self, new_class): module = inspect.getmodule(new_class) if hasattr(module, "__protobuf__"): @@ -107,6 +130,13 @@ def generate_file_pb(self, new_class, fallback_salt=""): for field in proto_plus_message._meta.fields.values(): if field.message and isinstance(field.message, str): field.message = self.messages[field.message] + elif field.enum and isinstance(field.enum, str): + field.enum = self.enums[field.enum] + + # Same thing for enums + for full_name, proto_plus_enum in self.enums.items(): + descriptor = pool.FindEnumTypeByName(full_name) + proto_plus_enum._meta.pb = descriptor # We no longer need to track this file's info; remove it from # the module's registry and from this object. @@ -130,14 +160,16 @@ def ready(self, new_class): """ # If there are any nested descriptors that have not been assigned to # the descriptors that should contain them, then we are not ready. - if len(self.nested): + if len(self.nested) or len(self.nested_enum): return False # If there are any unresolved fields (fields with a composite message # declared as a string), ensure that the corresponding message is # declared. for field in self.unresolved_fields: - if field.message not in self.messages: + if (field.message and field.message not in self.messages) or ( + field.enum and field.enum not in self.enums + ): return False # If the module in which this class is defined provides a @@ -156,5 +188,7 @@ def unresolved_fields(self): """Return fields with referencing message types as strings.""" for proto_plus_message in self.messages.values(): for field in proto_plus_message._meta.fields.values(): - if field.message and isinstance(field.message, str): + if (field.message and isinstance(field.message, str)) or ( + field.enum and isinstance(field.enum, str) + ): yield field diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index ef1af5912a1d..22e8f42debb6 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -14,6 +14,9 @@ import enum +from google.protobuf import descriptor_pb2 + +from proto import _file_info from proto import _package_info from proto.marshal.rules.enums import EnumRule @@ -30,12 +33,58 @@ def __new__(mcls, name, bases, attrs): # this component belongs within the file. package, marshal = _package_info.compile(name, attrs) + # Determine the local path of this proto component within the file. + local_path = tuple(attrs.get("__qualname__", name).split(".")) + + # Sanity check: We get the wrong full name if a class is declared + # inside a function local scope; correct this. + if "" in local_path: + ix = local_path.index("") + local_path = local_path[: ix - 1] + local_path[ix + 1 :] + + # Determine the full name in protocol buffers. + full_name = ".".join((package,) + local_path).lstrip(".") + filename = _file_info._FileInfo.proto_file_name( + attrs.get("__module__", name.lower()) + ) + enum_desc = descriptor_pb2.EnumDescriptorProto( + name=name, + # Note: the superclass ctor removes the variants, so get them now. + # Note: proto3 requires that the first variant value be zero. + value=sorted( + ( + descriptor_pb2.EnumValueDescriptorProto(name=name, number=number) + # Minor hack to get all the enum variants out. + for name, number in attrs.items() + if isinstance(number, int) + ), + key=lambda v: v.number, + ), + ) + + file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) + if len(local_path) == 1: + file_info.descriptor.enum_type.add().MergeFrom(enum_desc) + else: + file_info.nested_enum[local_path] = enum_desc + # Run the superclass constructor. cls = super().__new__(mcls, name, bases, attrs) + # We can't just add a "_meta" element to attrs because the Enum + # machinery doesn't know what to do with a non-int value. + # The pb is set later, in generate_file_pb + cls._meta = _EnumInfo(full_name=full_name, pb=None) + + file_info.enums[full_name] = cls + # Register the enum with the marshal. marshal.register(cls, EnumRule(cls)) + # Generate the descriptor for the file if it is ready. + if file_info.ready(new_class=cls): + file_info.generate_file_pb(new_class=cls, fallback_salt=full_name) + # Done; return the class. return cls @@ -44,3 +93,9 @@ class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): """A enum object that also builds a protobuf enum descriptor.""" pass + + +class _EnumInfo: + def __init__(self, *, full_name: str, pb): + self.full_name = full_name + self.pb = pb diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index c951a88c4708..cc98e8b01607 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -15,6 +15,7 @@ from enum import EnumMeta from google.protobuf import descriptor_pb2 +from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper from proto.primitives import ProtoType @@ -47,7 +48,7 @@ def __init__( if not isinstance(proto_type, int): # Note: We only support the "shortcut syntax" for enums # when receiving the actual class. - if isinstance(proto_type, EnumMeta): + if isinstance(proto_type, (EnumMeta, EnumTypeWrapper)): enum = proto_type proto_type = ProtoType.ENUM else: @@ -72,7 +73,6 @@ def __init__( def descriptor(self): """Return the descriptor for the field.""" if not self._descriptor: - proto_type = self.proto_type # Resolve the message type, if any, to a string. type_name = None if isinstance(self.message, str): @@ -85,29 +85,27 @@ def descriptor(self): type_name = ( self.message.DESCRIPTOR.full_name if hasattr(self.message, "DESCRIPTOR") - else self.message.meta.full_name + else self.message._meta.full_name ) + elif isinstance(self.enum, str): + if not self.enum.startswith(self.package): + self.enum = "{package}.{name}".format( + package=self.package, name=self.enum, + ) + type_name = self.enum elif self.enum: - # Nos decipiat. - # - # As far as the wire format is concerned, enums are int32s. - # Protocol buffers itself also only sends ints; the enum - # objects are simply helper classes for translating names - # and values and it is the user's job to resolve to an int. - # - # Therefore, the non-trivial effort of adding the actual - # enum descriptors seems to add little or no actual value. - # - # FIXME: Eventually, come back and put in the actual enum - # descriptors. - proto_type = ProtoType.INT32 + type_name = ( + self.enum.DESCRIPTOR.full_name + if hasattr(self.enum, "DESCRIPTOR") + else self.enum._meta.full_name + ) # Set the descriptor. self._descriptor = descriptor_pb2.FieldDescriptorProto( name=self.name, number=self.number, label=3 if self.repeated else 1, - type=proto_type, + type=self.proto_type, type_name=type_name, json_name=self.json_name, proto3_optional=self.optional, diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 4873126ca4ee..9de5630a8eb7 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -20,7 +20,6 @@ from google.protobuf import descriptor_pb2 from google.protobuf import message -from google.protobuf import symbol_database from google.protobuf.json_format import MessageToJson, Parse from proto import _file_info @@ -145,7 +144,6 @@ def __new__(mcls, name, bases, attrs): field_msg = field.message if hasattr(field_msg, "pb") and callable(field_msg.pb): field_msg = field_msg.pb() - # Sanity check: The field's message may not yet be defined if # it was a Message defined in the same file, and the file # descriptor proto has not yet been generated. @@ -154,7 +152,18 @@ def __new__(mcls, name, bases, attrs): # correctly when the file descriptor is created later. if field_msg: proto_imports.add(field_msg.DESCRIPTOR.file.name) - symbol_database.Default().RegisterMessage(field_msg) + + # Same thing, but for enums. + elif field.enum and not isinstance(field.enum, str): + field_enum = field.enum + field_enum = ( + field.enum._meta.pb + if hasattr(field.enum, "_meta") + else field.enum.DESCRIPTOR + ) + + if field_enum: + proto_imports.add(field_enum.file.name) # Increment the field index counter. index += 1 @@ -183,24 +192,13 @@ def __new__(mcls, name, bases, attrs): # Determine the filename. # We determine an appropriate proto filename based on the # Python module. - filename = "{0}.proto".format( - new_attrs.get("__module__", name.lower()).replace(".", "/") + filename = _file_info._FileInfo.proto_file_name( + new_attrs.get("__module__", name.lower()) ) # Get or create the information about the file, including the # descriptor to which the new message descriptor shall be added. - file_info = _file_info._FileInfo.registry.setdefault( - filename, - _file_info._FileInfo( - descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", - ), - enums=collections.OrderedDict(), - messages=collections.OrderedDict(), - name=filename, - nested={}, - ), - ) + file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) # Ensure any imports that would be necessary are assigned to the file # descriptor proto being created. @@ -227,6 +225,11 @@ def __new__(mcls, name, bases, attrs): for child_path in child_paths: desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path)) + # Same thing, but for enums + child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]] + for child_path in child_paths: + desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path)) + # Add the descriptor to the file if it is a top-level descriptor, # or to a "holding area" for nested messages otherwise. if len(local_path) == 1: @@ -325,17 +328,24 @@ def deserialize(cls, payload: bytes) -> "Message": """ return cls.wrap(cls.pb().FromString(payload)) - def to_json(cls, instance) -> str: + def to_json(cls, instance, *, use_integers_for_enums=True) -> str: """Given a message instance, serialize it to json Args: instance: An instance of this message type, or something 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. Returns: str: The json string representation of the protocol buffer. """ - return MessageToJson(cls.pb(instance)) + return MessageToJson( + cls.pb(instance), + use_integers_for_enums=use_integers_for_enums, + including_default_value_fields=True, + ) def from_json(cls, payload) -> "Message": """Given a json string representing an instance, diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 2711072c6600..6d08bfa0a611 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -37,6 +37,7 @@ platforms="Posix; MacOS X", include_package_data=True, install_requires=("protobuf >= 3.12.0",), + extras_require={"testing": ["google-api-core[grpc] >= 1.22.2",],}, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", diff --git a/packages/proto-plus/tests/clam.py b/packages/proto-plus/tests/clam.py new file mode 100644 index 000000000000..a4255fe8083d --- /dev/null +++ b/packages/proto-plus/tests/clam.py @@ -0,0 +1,29 @@ +# Copyright (C) 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + +__protobuf__ = proto.module(package="ocean.clam.v1", manifest={"Clam", "Species",},) + + +class Species(proto.Enum): + UNKNOWN = 0 + SQUAMOSA = 1 + DURASA = 2 + GIGAS = 3 + + +class Clam(proto.Message): + species = proto.Field(proto.ENUM, number=1, enum="Species") + mass_kg = proto.Field(proto.DOUBLE, number=2) diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index 2cf373527c00..7e7265f7beab 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -30,12 +30,19 @@ def pytest_runtest_setup(item): # Replace the descriptor pool and symbol database to avoid tests # polluting one another. - pool = descriptor_pool.DescriptorPool() + pool = type(descriptor_pool.Default())() sym_db = symbol_database.SymbolDatabase(pool=pool) - item._mocks = ( + item._mocks = [ mock.patch.object(descriptor_pool, "Default", return_value=pool), mock.patch.object(symbol_database, "Default", return_value=sym_db), - ) + ] + if descriptor_pool._USE_C_DESCRIPTORS: + from google.protobuf.pyext import _message + + item._mocks.append( + mock.patch("google.protobuf.pyext._message.default_pool", pool) + ) + [i.start() for i in item._mocks] # Importing a pb2 module registers those messages with the pool. diff --git a/packages/proto-plus/tests/mollusc.py b/packages/proto-plus/tests/mollusc.py new file mode 100644 index 000000000000..d12e4cb9b1b0 --- /dev/null +++ b/packages/proto-plus/tests/mollusc.py @@ -0,0 +1,22 @@ +# Copyright (C) 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto +import zone + +__protobuf__ = proto.module(package="ocean.mollusc.v1", manifest={"Mollusc",},) + + +class Mollusc(proto.Message): + zone = proto.Field(zone.Zone, number=1) diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index b494bd0c9adc..eeeab325c4a1 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -13,18 +13,19 @@ # limitations under the License. import proto +import sys def test_outer_enum_init(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color=Color.RED) assert foo.color == Color.RED assert foo.color == 1 @@ -34,15 +35,15 @@ class Foo(proto.Message): def test_outer_enum_init_int(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color=1) assert foo.color == Color.RED assert foo.color == 1 @@ -52,15 +53,15 @@ class Foo(proto.Message): def test_outer_enum_init_str(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color="RED") assert foo.color == Color.RED assert foo.color == 1 @@ -70,15 +71,15 @@ class Foo(proto.Message): def test_outer_enum_init_dict(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo({"color": 1}) assert foo.color == Color.RED assert foo.color == 1 @@ -88,15 +89,15 @@ class Foo(proto.Message): def test_outer_enum_init_dict_str(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo({"color": "BLUE"}) assert foo.color == Color.BLUE assert foo.color == 3 @@ -106,15 +107,15 @@ class Foo(proto.Message): def test_outer_enum_init_pb2(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo(Foo.pb()(color=Color.RED)) assert foo.color == Color.RED assert foo.color == 1 @@ -124,15 +125,15 @@ class Foo(proto.Message): def test_outer_enum_unset(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo() assert foo.color == Color.COLOR_UNSPECIFIED assert foo.color == 0 @@ -143,15 +144,15 @@ class Foo(proto.Message): def test_outer_enum_write(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo() foo.color = Color.GREEN assert foo.color == Color.GREEN @@ -161,15 +162,15 @@ class Foo(proto.Message): def test_outer_enum_write_int(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(proto.ENUM, number=1, enum=Color) - foo = Foo() foo.color = 3 assert foo.color == Color.BLUE @@ -180,15 +181,15 @@ class Foo(proto.Message): def test_outer_enum_write_str(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo() foo.color = "BLUE" assert foo.color == Color.BLUE @@ -206,7 +207,7 @@ class Color(proto.Enum): GREEN = 2 BLUE = 3 - color = proto.Field(proto.ENUM, number=1, enum=Color) + color = proto.Field(Color, number=1) foo = Foo(color=Foo.Color.RED) assert foo.color == Foo.Color.RED @@ -235,15 +236,15 @@ class Color(proto.Enum): def test_enum_del(): + class Foo(proto.Message): + color = proto.Field(proto.ENUM, number=1, enum="Color") + class Color(proto.Enum): COLOR_UNSPECIFIED = 0 RED = 1 GREEN = 2 BLUE = 3 - class Foo(proto.Message): - color = proto.Field(Color, number=1) - foo = Foo(color=Color.BLUE) del foo.color assert foo.color == Color.COLOR_UNSPECIFIED @@ -252,3 +253,103 @@ class Foo(proto.Message): assert "color" not in foo assert not foo.color assert Foo.pb(foo).color == 0 + + +def test_nested_enum_from_string(): + class Trawl(proto.Message): + # Note: this indirection with the nested field + # is necessary to trigger the exception for testing. + # Setting the field in an existing message accepts strings AND + # checks for valid variants. + # Similarly, constructing a message directly with a top level + # enum field kwarg passed as a string is also handled correctly, i.e. + # s = Squid(zone="ABYSSOPELAGIC") + # does NOT raise an exception. + squids = proto.RepeatedField("Squid", number=1) + + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + t = Trawl(squids=[{"zone": "MESOPELAGIC"}]) + assert t.squids[0] == Squid(zone=Zone.MESOPELAGIC) + + +def test_enum_field_by_string(): + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + s = Squid(zone=Zone.BATHYPELAGIC) + assert s.zone == Zone.BATHYPELAGIC + + +def test_enum_field_by_string_with_package(): + sys.modules[__name__].__protobuf__ = proto.module(package="mollusca.cephalopoda") + try: + + class Octopus(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="mollusca.cephalopoda.Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + finally: + del sys.modules[__name__].__protobuf__ + + o = Octopus(zone="MESOPELAGIC") + assert o.zone == Zone.MESOPELAGIC + + +def test_enums_in_different_files(): + import mollusc + import zone + + m = mollusc.Mollusc(zone="BATHYPELAGIC") + + assert m.zone == zone.Zone.BATHYPELAGIC + + +def test_enums_in_one_file(): + import clam + + c = clam.Clam(species=clam.Species.DURASA) + assert c.species == clam.Species.DURASA + + +def test_unwrapped_enum_fields(): + # The dayofweek_pb2 module apparently does some things that are deprecated + # in the protobuf API. + # There's nothing we can do about that, so ignore it. + import warnings + + warnings.filterwarnings("ignore", category=DeprecationWarning) + + from google.type import dayofweek_pb2 as dayofweek + + class Event(proto.Message): + weekday = proto.Field(proto.ENUM, number=1, enum=dayofweek.DayOfWeek) + + e = Event(weekday="WEDNESDAY") + e2 = Event.deserialize(Event.serialize(e)) + assert e == e2 + + class Task(proto.Message): + weekday = proto.Field(dayofweek.DayOfWeek, number=1) + + t = Task(weekday="TUESDAY") + t2 = Task.deserialize(Task.serialize(t)) + assert t == t2 diff --git a/packages/proto-plus/tests/test_file_info_salting.py b/packages/proto-plus/tests/test_file_info_salting.py index e87731cfbe39..c7a91d93a600 100644 --- a/packages/proto-plus/tests/test_file_info_salting.py +++ b/packages/proto-plus/tests/test_file_info_salting.py @@ -40,6 +40,7 @@ def sample_file_info(name): messages=collections.OrderedDict(), name=filename, nested={}, + nested_enum={}, ), ) diff --git a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py index 0a0e7c579e99..1fc5046226c4 100644 --- a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py +++ b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py @@ -55,6 +55,7 @@ def sample_file_info(name): messages=collections.OrderedDict(), name=filename, nested={}, + nested_enum={}, ), ) diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index a300a84b8f26..58195e89f311 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -50,3 +50,48 @@ class Squid(proto.Message): s2 = Squid.from_json(json) assert s == s2 + + +def test_json_stringy_enums(): + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + s1 = Squid(zone=Zone.MESOPELAGIC) + json = ( + Squid.to_json(s1, use_integers_for_enums=False) + .replace(" ", "") + .replace("\n", "") + ) + assert json == '{"zone":"MESOPELAGIC"}' + + s2 = Squid.from_json(json) + assert s2.zone == s1.zone + + +def test_json_default_enums(): + class Squid(proto.Message): + zone = proto.Field(proto.ENUM, number=1, enum="Zone") + + class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 + + s = Squid() + assert s.zone == Zone.EPIPELAGIC + json1 = Squid.to_json(s).replace(" ", "").replace("\n", "") + assert json1 == '{"zone":0}' + + json2 = ( + Squid.to_json(s, use_integers_for_enums=False) + .replace(" ", "") + .replace("\n", "") + ) + assert json2 == '{"zone":"EPIPELAGIC"}' diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py index 129f70161607..1d302053b3f5 100644 --- a/packages/proto-plus/tests/test_marshal_types_enum.py +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -18,6 +18,8 @@ import proto from proto.marshal.rules.enums import EnumRule +__protobuf__ = proto.module(package="test.marshal.enum") + def test_to_proto(): class Foo(proto.Enum): diff --git a/packages/proto-plus/tests/zone.py b/packages/proto-plus/tests/zone.py new file mode 100644 index 000000000000..4e7ef4023bb4 --- /dev/null +++ b/packages/proto-plus/tests/zone.py @@ -0,0 +1,26 @@ +# Copyright (C) 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import proto + + +__protobuf__ = proto.module(package="ocean.zone.v1", manifest={"Zone",},) + + +class Zone(proto.Enum): + EPIPELAGIC = 0 + MESOPELAGIC = 1 + BATHYPELAGIC = 2 + ABYSSOPELAGIC = 3 From 6e57ef042cdd67aebfa3de09a62aa5e6b4284189 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2020 15:58:05 -0700 Subject: [PATCH 076/272] chore: release 1.10.0-dev1 (#122) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index d99e60c15537..154e8f040ffd 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.10.0-dev1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev1) (2020-09-10) + + +### Bug Fixes + +* third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) + ### [1.9.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.0...v1.9.1) (2020-09-08) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 6d08bfa0a611..383c21ff6051 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.9.1" +version = "1.10.0-dev1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 70afd9e0c11f153e59be69f44ba5a21394aa9160 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 11 Sep 2020 11:33:26 -0600 Subject: [PATCH 077/272] fix: loosen tag match for publish_package (#123) Release-As: 1.10.0-dev2 --- packages/proto-plus/.circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index d1b2ab01e162..015ded9bac70 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -15,7 +15,7 @@ workflows: branches: ignore: /.*/ tags: - only: /^v\d+\.\d+\.\d+$/ + only: /^v\d+\.\d+\.\d+\.*$/ - unit-3.5: filters: tags: From 08c7c01015b50f8a202675c57339668df23d06d9 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2020 14:48:19 -0600 Subject: [PATCH 078/272] chore: release 1.10.0-dev2 (#124) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 8 ++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 154e8f040ffd..f03c3be39da9 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-11) + + +### Bug Fixes + +* loosen tag match for publish_package ([#123](https://www.github.com/googleapis/proto-plus-python/issues/123)) ([67441c9](https://www.github.com/googleapis/proto-plus-python/commit/67441c931b5f00b2e1084ce2539784ae9d9c31e6)) +* third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) + ## [1.10.0-dev1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev1) (2020-09-10) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 383c21ff6051..0414810a50a2 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.10.0-dev1" +version = "1.10.0-dev2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 18e7f70657b12a71e692ad3c72a725c16ae516b7 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 15 Sep 2020 11:24:17 -0600 Subject: [PATCH 079/272] chore: fix regex match for release tags (#125) Alllow matches for pre-releases like 1.0.0-dev1. --- packages/proto-plus/.circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index 015ded9bac70..2df0719c6979 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -7,7 +7,7 @@ workflows: - style-check: filters: tags: - only: /^v\d+\.\d+\.\d+$/ + only: /^v\d+\.\d+\.\d+.*/ - publish_package: requires: - style-check @@ -15,7 +15,7 @@ workflows: branches: ignore: /.*/ tags: - only: /^v\d+\.\d+\.\d+\.*$/ + only: /^v\d+\.\d+\.\d+.*/ - unit-3.5: filters: tags: From 2a06d8d153961e82de31a1df7dce02056ae6d551 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2020 10:29:00 -0700 Subject: [PATCH 080/272] chore: release 1.10.0-dev2 (#126) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index f03c3be39da9..6ac89259ef10 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-15) + + +### Bug Fixes + +* loosen tag match for publish_package ([#123](https://www.github.com/googleapis/proto-plus-python/issues/123)) ([67441c9](https://www.github.com/googleapis/proto-plus-python/commit/67441c931b5f00b2e1084ce2539784ae9d9c31e6)) +* third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) + ## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-11) From 4f54770fcbed09f1b1992bb103ca7ece6418f3a9 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Mon, 21 Sep 2020 07:46:08 -0700 Subject: [PATCH 081/272] chore: add CODEOWNERS and repo sync (#127) --- packages/proto-plus/.github/CODEOWNERS | 7 +++++ .../.github/sync-repo-settings.yaml | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 packages/proto-plus/.github/CODEOWNERS create mode 100644 packages/proto-plus/.github/sync-repo-settings.yaml diff --git a/packages/proto-plus/.github/CODEOWNERS b/packages/proto-plus/.github/CODEOWNERS new file mode 100644 index 000000000000..aaabff6ec140 --- /dev/null +++ b/packages/proto-plus/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Code owners file. +# This file controls who is tagged for review for any given pull request. + +# For syntax help see: +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax + +* @googleapis/actools @googleapis/yoshi-python diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml new file mode 100644 index 000000000000..c67bd0954314 --- /dev/null +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -0,0 +1,26 @@ +rebaseMergeAllowed: false +squashMergeAllowed: true +mergeCommitAllowed: false +branchProtectionRules: +- pattern: master + isAdminEnforced: true + requiredStatusCheckContexts: + - 'ci/circleci: docs' + - 'ci/circleci: style-check' + - 'ci/circleci: unit-3.5' + - 'ci/circleci: unit-3.6' + - 'ci/circleci: unit-3.7' + - 'ci/circleci: unit-3.8' + - 'ci/circleci: unit-cpp-3.5' + - 'ci/circleci: unit-cpp-3.6' + - 'ci/circleci: unit-cpp-3.7' + - 'ci/circleci: unit-cpp-3.8' + - 'cla/google' + requiredApprovingReviewCount: 1 + requiresCodeOwnerReviews: true + requiresStrictStatusChecks: true +permissionRules: + - team: yoshi-python + permission: push + - team: actools + permission: admin From bb9fb7fa39258167ae96056f8e75b750daf6ce6d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2020 08:18:37 -0700 Subject: [PATCH 082/272] chore: release 1.10.0-dev2 (#128) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 6ac89259ef10..9c61f1e23bdb 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-21) + + +### Bug Fixes + +* loosen tag match for publish_package ([#123](https://www.github.com/googleapis/proto-plus-python/issues/123)) ([67441c9](https://www.github.com/googleapis/proto-plus-python/commit/67441c931b5f00b2e1084ce2539784ae9d9c31e6)) +* third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) + ## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-15) From 82c719fb5fedce63e1e065b9d681fbd99b0e2ff4 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 21 Sep 2020 09:06:35 -0700 Subject: [PATCH 083/272] feat: nullable ListValue and Struct fields (#129) Assume a message type with a ListValue field. When a message instance is created with no value passed for the ListValue field, the field is None when accessed via Python. Assume a message type with a Struct field. When a message instance is created with no value passed for the Struct field, the field is None when accessed via Python. Impl/fix for #106 --- packages/proto-plus/docs/marshal.rst | 4 ++-- .../proto-plus/proto/marshal/rules/struct.py | 10 ++++++++-- .../tests/test_marshal_types_struct.py | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/docs/marshal.rst b/packages/proto-plus/docs/marshal.rst index 761f297c2855..369d823d6472 100644 --- a/packages/proto-plus/docs/marshal.rst +++ b/packages/proto-plus/docs/marshal.rst @@ -23,9 +23,9 @@ Protocol buffer type Python type Nullable ``google.protobuf.FloatValue`` ``float`` Yes ``google.protobuf.Int32Value`` ``int`` Yes ``google.protobuf.Int64Value`` ``int`` Yes -``google.protobuf.ListValue`` ``MutableSequence`` – +``google.protobuf.ListValue`` ``MutableSequence`` Yes ``google.protobuf.StringValue`` ``str`` Yes -``google.protobuf.Struct`` ``MutableMapping`` – +``google.protobuf.Struct`` ``MutableMapping`` Yes ``google.protobuf.Timestamp`` ``datetime.datetime`` Yes ``google.protobuf.UInt32Value`` ``int`` Yes ``google.protobuf.UInt64Value`` ``int`` Yes diff --git a/packages/proto-plus/proto/marshal/rules/struct.py b/packages/proto-plus/proto/marshal/rules/struct.py index dc183407c026..53ef34fa1600 100644 --- a/packages/proto-plus/proto/marshal/rules/struct.py +++ b/packages/proto-plus/proto/marshal/rules/struct.py @@ -82,7 +82,11 @@ def __init__(self, *, marshal): def to_python(self, value, *, absent: bool = None): """Coerce the given value to a Python sequence.""" - return repeated.RepeatedComposite(value.values, marshal=self._marshal) + return ( + None + if absent + else repeated.RepeatedComposite(value.values, marshal=self._marshal) + ) def to_proto(self, value) -> struct_pb2.ListValue: # We got a proto, or else something we sent originally. @@ -106,7 +110,9 @@ def __init__(self, *, marshal): def to_python(self, value, *, absent: bool = None): """Coerce the given value to a Python mapping.""" - return maps.MapComposite(value.fields, marshal=self._marshal) + return ( + None if absent else maps.MapComposite(value.fields, marshal=self._marshal) + ) def to_proto(self, value) -> struct_pb2.Struct: # We got a proto, or else something we sent originally. diff --git a/packages/proto-plus/tests/test_marshal_types_struct.py b/packages/proto-plus/tests/test_marshal_types_struct.py index 74150f6ec011..82a40c220fdd 100644 --- a/packages/proto-plus/tests/test_marshal_types_struct.py +++ b/packages/proto-plus/tests/test_marshal_types_struct.py @@ -64,6 +64,22 @@ class Foo(proto.Message): assert foo.value == ["3", None, "foo", True] +def test_value_lists_null(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.ListValue, number=1) + + foo = Foo() + assert foo.value is None + + +def test_value_struct_null(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Struct, number=1) + + foo = Foo() + assert foo.value is None + + def test_value_lists_rmw(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) From eef8a67af2eb6ba89753a99c868d668979d7bdbe Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 21 Sep 2020 14:57:28 -0600 Subject: [PATCH 084/272] docs: link to pypi protobuf package (#131) Release-As: 1.10.0 --- packages/proto-plus/docs/index.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/docs/index.rst b/packages/proto-plus/docs/index.rst index 0225095ebe4e..f12b7e7c6d9e 100644 --- a/packages/proto-plus/docs/index.rst +++ b/packages/proto-plus/docs/index.rst @@ -21,7 +21,9 @@ Install this library using ``pip``: $ pip install proto-plus This library carries a dependency on the official implementation -(``protobuf``), which may install a C component. +(protobuf_), which may install a C component. + +.. _protobuf: https://pypi.org/project/protobuf/ Table of Contents From d1a12bbec7f0209ce6e1a52ca005fdd49900aa28 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 24 Sep 2020 15:43:06 -0600 Subject: [PATCH 085/272] chore: release 1.10.0 (#136) --- packages/proto-plus/CHANGELOG.md | 17 ++--------------- packages/proto-plus/setup.py | 2 +- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 9c61f1e23bdb..fb81084130cc 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-21) +## [1.10.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0) (2020-09-24) ### Bug Fixes @@ -8,15 +8,8 @@ * loosen tag match for publish_package ([#123](https://www.github.com/googleapis/proto-plus-python/issues/123)) ([67441c9](https://www.github.com/googleapis/proto-plus-python/commit/67441c931b5f00b2e1084ce2539784ae9d9c31e6)) * third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) -## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-15) - - -### Bug Fixes - -* loosen tag match for publish_package ([#123](https://www.github.com/googleapis/proto-plus-python/issues/123)) ([67441c9](https://www.github.com/googleapis/proto-plus-python/commit/67441c931b5f00b2e1084ce2539784ae9d9c31e6)) -* third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) -## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-11) +## [1.10.0-dev2](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev2) (2020-09-21) ### Bug Fixes @@ -24,12 +17,6 @@ * loosen tag match for publish_package ([#123](https://www.github.com/googleapis/proto-plus-python/issues/123)) ([67441c9](https://www.github.com/googleapis/proto-plus-python/commit/67441c931b5f00b2e1084ce2539784ae9d9c31e6)) * third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) -## [1.10.0-dev1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0-dev1) (2020-09-10) - - -### Bug Fixes - -* third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) ### [1.9.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.0...v1.9.1) (2020-09-08) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 0414810a50a2..4ce2e91eb08b 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.10.0-dev2" +version = "1.10.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From ba41daea578a8e217d2117464e6f20098ae2e269 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 1 Oct 2020 14:39:29 -0700 Subject: [PATCH 086/272] fix: accessing an unset struct_pb2.Value field does not raise (#140) class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) f = Foo() assert f.value is None # This should not raise an exception. assert "value" not in f # The attribute has _not_ been set. f.value = None assert f.value is None assert "value" in f # The attribute _has_ been set. None of the above should raise an exception. --- packages/proto-plus/proto/marshal/rules/struct.py | 14 ++++++++++---- packages/proto-plus/tests/conftest.py | 8 ++++---- .../proto-plus/tests/test_marshal_types_struct.py | 9 ++++++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/proto-plus/proto/marshal/rules/struct.py b/packages/proto-plus/proto/marshal/rules/struct.py index 53ef34fa1600..27dfaf566a70 100644 --- a/packages/proto-plus/proto/marshal/rules/struct.py +++ b/packages/proto-plus/proto/marshal/rules/struct.py @@ -29,11 +29,15 @@ def __init__(self, *, marshal): def to_python(self, value, *, absent: bool = None): """Coerce the given value to the appropriate Python type. - Note that setting ``null_value`` is distinct from not setting - a value, and the absent value will raise an exception. + Note that both NullValue and absent fields return None. + In order to disambiguate between these two options, + use containment check, + E.g. + "value" in foo + which is True for NullValue and False for an absent value. """ kind = value.WhichOneof("kind") - if kind == "null_value": + if kind == "null_value" or absent: return None if kind == "bool_value": return bool(value.bool_value) @@ -49,7 +53,9 @@ def to_python(self, value, *, absent: bool = None): return self._marshal.to_python( struct_pb2.ListValue, value.list_value, absent=False, ) - raise AttributeError + # If more variants are ever added, we want to fail loudly + # instead of tacitly returning None. + raise ValueError("Unexpected kind: %s" % kind) # pragma: NO COVER def to_proto(self, value) -> struct_pb2.Value: """Return a protobuf Value object representing this value.""" diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index 7e7265f7beab..b60b91d2985a 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import imp +import importlib from unittest import mock from google.protobuf import descriptor_pool @@ -76,11 +76,11 @@ def pytest_runtest_setup(item): # If the marshal had previously registered the old message classes, # then reload the appropriate modules so the marshal is using the new ones. if "wrappers_pb2" in reloaded: - imp.reload(rules.wrappers) + importlib.reload(rules.wrappers) if "struct_pb2" in reloaded: - imp.reload(rules.struct) + importlib.reload(rules.struct) if reloaded.intersection({"timestamp_pb2", "duration_pb2"}): - imp.reload(rules.dates) + importlib.reload(rules.dates) def pytest_runtest_teardown(item): diff --git a/packages/proto-plus/tests/test_marshal_types_struct.py b/packages/proto-plus/tests/test_marshal_types_struct.py index 82a40c220fdd..914730c5745c 100644 --- a/packages/proto-plus/tests/test_marshal_types_struct.py +++ b/packages/proto-plus/tests/test_marshal_types_struct.py @@ -30,6 +30,13 @@ class Foo(proto.Message): assert Foo(value=True).value is True +def test_value_absent(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + assert Foo().value is None + + def test_value_primitives_rmw(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) @@ -158,7 +165,7 @@ class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) foo = Foo() - assert not hasattr(foo, "value") + assert "value" not in foo def test_list_value_read(): From af0edc169948190683117df7dc8648302cc4abb8 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 8 Oct 2020 13:23:05 -0600 Subject: [PATCH 087/272] fix: add LICENSE and tests to package (#146) --- packages/proto-plus/MANIFEST.in | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/proto-plus/MANIFEST.in diff --git a/packages/proto-plus/MANIFEST.in b/packages/proto-plus/MANIFEST.in new file mode 100644 index 000000000000..e6c966606293 --- /dev/null +++ b/packages/proto-plus/MANIFEST.in @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include README.rst LICENSE +recursive-include tests * +global-exclude *.py[co] +global-exclude __pycache__ \ No newline at end of file From 1b18fd51fcfe8e82d6da6b1e05c7ddcf2a2b4e0f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2020 19:26:03 +0000 Subject: [PATCH 088/272] chore: release 1.10.1 (#147) :robot: I have created a release \*beep\* \*boop\* --- ### [1.10.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.0...v1.10.1) (2020-10-08) ### Bug Fixes * accessing an unset struct_pb2.Value field does not raise ([#140](https://www.github.com/googleapis/proto-plus-python/issues/140)) ([d045cbf](https://www.github.com/googleapis/proto-plus-python/commit/d045cbf058cbb8f4ca98dd06741270fcaee865be)) * add LICENSE and tests to package ([#146](https://www.github.com/googleapis/proto-plus-python/issues/146)) ([815c943](https://www.github.com/googleapis/proto-plus-python/commit/815c9439a1dadb2d4111784eb18ba673ce6e6cc2)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/proto-plus/CHANGELOG.md | 8 ++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index fb81084130cc..a38d8958b3a0 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### [1.10.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.0...v1.10.1) (2020-10-08) + + +### Bug Fixes + +* accessing an unset struct_pb2.Value field does not raise ([#140](https://www.github.com/googleapis/proto-plus-python/issues/140)) ([d045cbf](https://www.github.com/googleapis/proto-plus-python/commit/d045cbf058cbb8f4ca98dd06741270fcaee865be)) +* add LICENSE and tests to package ([#146](https://www.github.com/googleapis/proto-plus-python/issues/146)) ([815c943](https://www.github.com/googleapis/proto-plus-python/commit/815c9439a1dadb2d4111784eb18ba673ce6e6cc2)) + ## [1.10.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0) (2020-09-24) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 4ce2e91eb08b..695611a6be76 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.10.0" +version = "1.10.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 82feb3f517d5f746e370b10baa5e911c1234c084 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 14 Oct 2020 14:55:06 -0600 Subject: [PATCH 089/272] docs: explain how to use repeated struct.Value (#148) Closes #104 Explain how to set fields of repeated struct.Value Add datetime_helpers to reference docs Add wrap, to_json, and from_json to reference docs Fetch correct package version for docs/conf.py --- packages/proto-plus/docs/conf.py | 10 +++--- packages/proto-plus/docs/fields.rst | 33 +++++++++++++++++++ .../docs/reference/datetime_helpers.rst | 5 +++ packages/proto-plus/docs/reference/index.rst | 5 +-- .../proto-plus/docs/reference/message.rst | 3 ++ 5 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 packages/proto-plus/docs/reference/datetime_helpers.rst diff --git a/packages/proto-plus/docs/conf.py b/packages/proto-plus/docs/conf.py index 6a4550853854..4c77371ebd21 100644 --- a/packages/proto-plus/docs/conf.py +++ b/packages/proto-plus/docs/conf.py @@ -15,6 +15,9 @@ import os import sys +import pkg_resources + + sys.path.insert(0, os.path.abspath("..")) @@ -22,12 +25,9 @@ project = "Proto Plus for Python" copyright = "2018, Google LLC" -author = "Luke Sneeringer" +author = "Google LLC" -# The short X.Y version -version = "0.1.0" -# The full version, including alpha/beta/rc tags -release = "0.1.0" +version = pkg_resources.get_distribution("proto-plus").version # -- General configuration --------------------------------------------------- diff --git a/packages/proto-plus/docs/fields.rst b/packages/proto-plus/docs/fields.rst index b4aa24259f70..07e70fbf5bc6 100644 --- a/packages/proto-plus/docs/fields.rst +++ b/packages/proto-plus/docs/fields.rst @@ -56,6 +56,36 @@ Declare them in Python using the :class:`~.RepeatedField` class: publisher = proto.Field(proto.STRING, number=2) +.. note:: + + Elements **must** be appended individually for repeated fields of `struct.Value`. + + .. code-block:: python + + class Row(proto.Message): + values = proto.RepeatedField(proto.MESSAGE, number=1, message=struct.Value,) + + >>> row = Row() + >>> values = [struct_pb2.Value(string_value="hello")] + >>> for v in values: + >>> row.values.append(v) + + Direct assignment will result in an error. + + .. code-block:: python + + class Row(proto.Message): + values = proto.RepeatedField(proto.MESSAGE, number=1, message=struct.Value,) + + >>> row = Row() + >>> row.values = [struct_pb2.Value(string_value="hello")] + Traceback (most recent call last): + File "", line 1, in + File "/usr/local/google/home/busunkim/github/python-automl/.nox/unit-3-8/lib/python3.8/site-packages/proto/message.py", line 543, in __setattr__ + self._pb.MergeFrom(self._meta.pb(**{key: pb_value})) + TypeError: Value must be iterable + + Map fields ---------- @@ -168,3 +198,6 @@ one-variant ``oneof``. See the protocolbuffers documentation_ for more information. .. _documentation: https://github.com/protocolbuffers/protobuf/blob/v3.12.0/docs/field_presence.md + + + diff --git a/packages/proto-plus/docs/reference/datetime_helpers.rst b/packages/proto-plus/docs/reference/datetime_helpers.rst new file mode 100644 index 000000000000..e45ad1fe4c39 --- /dev/null +++ b/packages/proto-plus/docs/reference/datetime_helpers.rst @@ -0,0 +1,5 @@ +Datetime Helpers +---------------- + +.. automodule:: proto.datetime_helpers + :members: diff --git a/packages/proto-plus/docs/reference/index.rst b/packages/proto-plus/docs/reference/index.rst index 85af2afe6edb..35b01946e51f 100644 --- a/packages/proto-plus/docs/reference/index.rst +++ b/packages/proto-plus/docs/reference/index.rst @@ -4,15 +4,16 @@ Reference Below is a reference for the major classes and functions within this module. -It is split into two main sections: - - The :doc:`message` section (which uses the ``message`` and ``fields`` modules) handles constructing messages. - The :doc:`marshal` module handles translating between internal protocol buffer instances and idiomatic equivalents. +- The :doc:`datetime_helpers` has datetime related helpers to maintain + nanosecond precision. .. toctree:: :maxdepth: 2 message marshal + datetime_helpers diff --git a/packages/proto-plus/docs/reference/message.rst b/packages/proto-plus/docs/reference/message.rst index 1d9e070c1f6c..d9752b5dccee 100644 --- a/packages/proto-plus/docs/reference/message.rst +++ b/packages/proto-plus/docs/reference/message.rst @@ -5,8 +5,11 @@ Message and Field :members: .. automethod:: pb + .. automethod:: wrap .. automethod:: serialize .. automethod:: deserialize + .. automethod:: to_json + .. automethod:: from_json .. automodule:: proto.fields From b4247ac5282fae23422a7f99f98fd2f582b1384e Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2020 13:57:50 -0700 Subject: [PATCH 090/272] chore: release 1.10.2 (#149) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index a38d8958b3a0..6ac6d051fb03 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.10.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.1...v1.10.2) (2020-10-14) + + +### Documentation + +* explain how to use repeated struct.Value ([#148](https://www.github.com/googleapis/proto-plus-python/issues/148)) ([9634ea8](https://www.github.com/googleapis/proto-plus-python/commit/9634ea8fa464c0d34f13469016f23cc2e986d973)), closes [#104](https://www.github.com/googleapis/proto-plus-python/issues/104) + ### [1.10.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.0...v1.10.1) (2020-10-08) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 695611a6be76..cf5df00e590e 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.10.1" +version = "1.10.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From bcf5a040e7bc10db3f5ec26ed3258a7093a60bea Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 19 Oct 2020 14:46:02 -0700 Subject: [PATCH 091/272] feat: provide a to_dict method (#154) Parallels the `to_json` method. Also adds options to ignore unknown fields during deserializtion. Closes #153 Closes #151 --- packages/proto-plus/docs/messages.rst | 10 +++ .../proto-plus/docs/reference/message.rst | 1 + packages/proto-plus/noxfile.py | 2 +- packages/proto-plus/proto/message.py | 59 ++++++++++++---- packages/proto-plus/tests/test_json.py | 22 +++++- packages/proto-plus/tests/test_message.py | 67 +++++++++++++++++++ 6 files changed, 146 insertions(+), 15 deletions(-) diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index 18dd4db60bef..af23ea9da59e 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -165,3 +165,13 @@ via the :meth:`~.Message.to_json` and :meth:`~.Message.from_json` methods. new_song = Song.from_json(json) +Similarly, messages can be converted into dictionaries via the +:meth:`~.Message.to_dict` helper method. +There is no :meth:`~.Message.from_dict` method because the Message constructor +already allows construction from mapping types. + +.. code-block:: python + + song_dict = Song.to_dict(song) + + new_song = Song(song_dict) diff --git a/packages/proto-plus/docs/reference/message.rst b/packages/proto-plus/docs/reference/message.rst index d9752b5dccee..34da8376fbe9 100644 --- a/packages/proto-plus/docs/reference/message.rst +++ b/packages/proto-plus/docs/reference/message.rst @@ -10,6 +10,7 @@ Message and Field .. automethod:: deserialize .. automethod:: to_json .. automethod:: from_json + .. automethod:: to_dict .. automodule:: proto.fields diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 2f323155d6b2..3a2fff281150 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -48,7 +48,7 @@ def unitcpp(session): return unit(session, proto="cpp") -@nox.session(python="3.6") +@nox.session(python="3.7") def docs(session): """Build the docs.""" diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 9de5630a8eb7..f1c3bbbe37b8 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -20,7 +20,7 @@ from google.protobuf import descriptor_pb2 from google.protobuf import message -from google.protobuf.json_format import MessageToJson, Parse +from google.protobuf.json_format import MessageToDict, MessageToJson, Parse from proto import _file_info from proto import _package_info @@ -347,21 +347,45 @@ def to_json(cls, instance, *, use_integers_for_enums=True) -> str: including_default_value_fields=True, ) - def from_json(cls, payload) -> "Message": + def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": """Given a json string representing an instance, parse it into a message. Args: paylod: A json string representing a message. + ignore_unknown_fields (Optional(bool)): If True, do not raise errors + for unknown fields. Returns: ~.Message: An instance of the message class against which this method was called. """ instance = cls() - Parse(payload, instance._pb) + Parse(payload, instance._pb, ignore_unknown_fields=ignore_unknown_fields) return instance + def to_dict(cls, instance, *, use_integers_for_enums=True) -> "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). + 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. + + 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=True, + preserving_proto_field_name=True, + use_integers_for_enums=use_integers_for_enums, + ) + class Message(metaclass=MessageMeta): """The abstract base class for a message. @@ -369,17 +393,19 @@ class Message(metaclass=MessageMeta): Args: mapping (Union[dict, ~.Message]): A dictionary or message to be used to determine the values for this message. + ignore_unknown_fields (Optional(bool)): If True, do not raise errors for + unknown fields. Only applied if `mapping` is a mapping type or there + are keyword parameters. kwargs (dict): Keys and values corresponding to the fields of the message. """ - def __init__(self, mapping=None, **kwargs): + def __init__(self, mapping=None, *, ignore_unknown_fields=False, **kwargs): # We accept several things for `mapping`: # * An instance of this class. # * An instance of the underlying protobuf descriptor class. # * A dict # * Nothing (keyword arguments only). - if mapping is None: if not kwargs: # Special fast path for empty construction. @@ -405,24 +431,33 @@ def __init__(self, mapping=None, **kwargs): # Just use the above logic on mapping's underlying pb. self.__init__(mapping=mapping._pb, **kwargs) return - elif not isinstance(mapping, collections.abc.Mapping): + elif isinstance(mapping, collections.abc.Mapping): + # Can't have side effects on mapping. + mapping = copy.copy(mapping) + # kwargs entries take priority for duplicate keys. + mapping.update(kwargs) + else: # Sanity check: Did we get something not a map? Error if so. raise TypeError( "Invalid constructor input for %s: %r" % (self.__class__.__name__, mapping,) ) - else: - # Can't have side effects on mapping. - mapping = copy.copy(mapping) - # kwargs entries take priority for duplicate keys. - mapping.update(kwargs) params = {} # Update the mapping to address any values that need to be # coerced. marshal = self._meta.marshal for key, value in mapping.items(): - pb_type = self._meta.fields[key].pb_type + try: + pb_type = self._meta.fields[key].pb_type + except KeyError: + if ignore_unknown_fields: + continue + + raise ValueError( + "Unknown field for {}: {}".format(self.__class__.__name__, key) + ) + pb_value = marshal.to_proto(pb_type, value) if pb_value is not None: params[key] = pb_value diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index 58195e89f311..34c2d438a65a 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -15,7 +15,7 @@ import pytest import proto -from google.protobuf.json_format import MessageToJson, Parse +from google.protobuf.json_format import MessageToJson, Parse, ParseError def test_message_to_json(): @@ -34,7 +34,7 @@ class Squid(proto.Message): json = """{ "massKg": 100 - } + } """ s = Squid.from_json(json) @@ -95,3 +95,21 @@ class Zone(proto.Enum): .replace("\n", "") ) assert json2 == '{"zone":"EPIPELAGIC"}' + + +def test_json_unknown_field(): + # Note that 'lengthCm' is unknown in the local definition. + # This could happen if the client is using an older proto definition + # than the server. + json_str = '{\n "massKg": 20,\n "lengthCm": 100\n}' + + class Octopus(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + o = Octopus.from_json(json_str, ignore_unknown_fields=True) + assert not hasattr(o, "length_cm") + assert not hasattr(o, "lengthCm") + + # Don't permit unknown fields by default + with pytest.raises(ParseError): + o = Octopus.from_json(json_str) diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index b5f598571335..32a6180223a8 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools import pytest import proto @@ -228,3 +229,69 @@ class Squid(proto.Message): s1._pb = s2._pb assert s1.mass_kg == 20 + + +def test_serialize_to_dict(): + class Squid(proto.Message): + # Test primitives, enums, and repeated fields. + class Chromatophore(proto.Message): + class Color(proto.Enum): + UNKNOWN = 0 + RED = 1 + BROWN = 2 + WHITE = 3 + BLUE = 4 + + color = proto.Field(Color, number=1) + + mass_kg = proto.Field(proto.INT32, number=1) + chromatophores = proto.RepeatedField(Chromatophore, number=2) + + s = Squid(mass_kg=20) + colors = ["RED", "BROWN", "WHITE", "BLUE"] + s.chromatophores = [ + {"color": c} for c in itertools.islice(itertools.cycle(colors), 10) + ] + + s_dict = Squid.to_dict(s) + assert s_dict["chromatophores"][0]["color"] == 1 + + new_s = Squid(s_dict) + assert new_s == s + + s_dict = Squid.to_dict(s, use_integers_for_enums=False) + assert s_dict["chromatophores"][0]["color"] == "RED" + + new_s = Squid(s_dict) + assert new_s == s + + +def test_unknown_field_deserialize(): + # This is a somewhat common setup: a client uses an older proto definition, + # while the server sends the newer definition. The client still needs to be + # able to interact with the protos it receives from the server. + + class Octopus_Old(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + class Octopus_New(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + length_cm = proto.Field(proto.INT32, number=2) + + o_new = Octopus_New(mass_kg=20, length_cm=100) + o_ser = Octopus_New.serialize(o_new) + + o_old = Octopus_Old.deserialize(o_ser) + assert not hasattr(o_old, "length_cm") + + +def test_unknown_field_from_dict(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + # By default we don't permit unknown fields + with pytest.raises(ValueError): + s = Squid({"mass_kg": 20, "length_cm": 100}) + + s = Squid({"mass_kg": 20, "length_cm": 100}, ignore_unknown_fields=True) + assert not hasattr(s, "length_cm") From 5b3d326f053edb2f94e306bacf8d5c6a507800cf Mon Sep 17 00:00:00 2001 From: Noah Dietz Date: Mon, 19 Oct 2020 14:50:03 -0700 Subject: [PATCH 092/272] chore: update CODEOWNERS actools to actools-python (#155) --- packages/proto-plus/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.github/CODEOWNERS b/packages/proto-plus/.github/CODEOWNERS index aaabff6ec140..0546e1d81418 100644 --- a/packages/proto-plus/.github/CODEOWNERS +++ b/packages/proto-plus/.github/CODEOWNERS @@ -4,4 +4,4 @@ # For syntax help see: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax -* @googleapis/actools @googleapis/yoshi-python +* @googleapis/actools-python @googleapis/yoshi-python From 05d09f33e473594a0406892e85b1b147c089b9b4 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2020 22:00:03 +0000 Subject: [PATCH 093/272] chore: release 1.11.0 (#156) :robot: I have created a release \*beep\* \*boop\* --- ## [1.11.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.2...v1.11.0) (2020-10-19) ### Features * provide a to_dict method ([#154](https://www.github.com/googleapis/proto-plus-python/issues/154)) ([ccf903e](https://www.github.com/googleapis/proto-plus-python/commit/ccf903e3cddfcb1ff539e853594b4342914b7d61)), closes [#153](https://www.github.com/googleapis/proto-plus-python/issues/153) [#151](https://www.github.com/googleapis/proto-plus-python/issues/151) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 6ac6d051fb03..fccdae02ced8 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.11.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.2...v1.11.0) (2020-10-19) + + +### Features + +* provide a to_dict method ([#154](https://www.github.com/googleapis/proto-plus-python/issues/154)) ([ccf903e](https://www.github.com/googleapis/proto-plus-python/commit/ccf903e3cddfcb1ff539e853594b4342914b7d61)), closes [#153](https://www.github.com/googleapis/proto-plus-python/issues/153) [#151](https://www.github.com/googleapis/proto-plus-python/issues/151) + ### [1.10.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.1...v1.10.2) (2020-10-14) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index cf5df00e590e..fb5c7d4246a0 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.10.2" +version = "1.11.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From c6e017c579c2a09c671bce49f6b7a74d6545c3c7 Mon Sep 17 00:00:00 2001 From: Noah Dietz Date: Thu, 22 Oct 2020 10:48:20 -0700 Subject: [PATCH 094/272] chore: update actools-python perm in sync settings (#158) --- packages/proto-plus/.github/sync-repo-settings.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index c67bd0954314..9d1600c80a12 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -24,3 +24,5 @@ permissionRules: permission: push - team: actools permission: admin + - team: actools-python + permission: push From 1f7785008b4043e27a31ef162fa79ba6a02550b7 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 3 Nov 2020 12:58:07 -0800 Subject: [PATCH 095/272] test: unknown fields are preserved (#160) * test: unknown fields are preserved Consider the following: ```proto message Old { string name = 1; } message New { string name = 1; string path = 2; } ``` We can think of `New` as being a minor version release update of `Old`. If a client using the older version receives a message over the wire from a server using the newer version, it is desirable that any new, unknown fields are preserved. These can store context the server needs, which is important in get/modify/set loops. --- packages/proto-plus/proto/message.py | 1 - packages/proto-plus/tests/test_message.py | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index f1c3bbbe37b8..08e6623f8123 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -155,7 +155,6 @@ def __new__(mcls, name, bases, attrs): # Same thing, but for enums. elif field.enum and not isinstance(field.enum, str): - field_enum = field.enum field_enum = ( field.enum._meta.pb if hasattr(field.enum, "_meta") diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index 32a6180223a8..b723fcdeaf60 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -285,6 +285,28 @@ class Octopus_New(proto.Message): assert not hasattr(o_old, "length_cm") +def test_unknown_field_deserialize_keep_fields(): + # This is a somewhat common setup: a client uses an older proto definition, + # while the server sends the newer definition. The client still needs to be + # able to interact with the protos it receives from the server. + + class Octopus_Old(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + class Octopus_New(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + length_cm = proto.Field(proto.INT32, number=2) + + o_new = Octopus_New(mass_kg=20, length_cm=100) + o_ser = Octopus_New.serialize(o_new) + + o_old = Octopus_Old.deserialize(o_ser) + assert not hasattr(o_old, "length_cm") + + o_new = Octopus_New.deserialize(Octopus_Old.serialize(o_old)) + assert o_new.length_cm == 100 + + def test_unknown_field_from_dict(): class Squid(proto.Message): mass_kg = proto.Field(proto.INT32, number=1) From 88b56c86b7cf422ab401c90e1961222ae6851c1d Mon Sep 17 00:00:00 2001 From: yon-mg <71726126+yon-mg@users.noreply.github.com> Date: Fri, 20 Nov 2020 15:49:53 -0800 Subject: [PATCH 096/272] feat: add default values parameter to to_json (#164) * feat: add default values parameter to to_json * fix: typing & stylecheck issues --- packages/proto-plus/proto/message.py | 10 ++++++++-- packages/proto-plus/tests/test_json.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 08e6623f8123..28a87a806a8f 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -327,7 +327,13 @@ def deserialize(cls, payload: bytes) -> "Message": """ return cls.wrap(cls.pb().FromString(payload)) - def to_json(cls, instance, *, use_integers_for_enums=True) -> str: + def to_json( + cls, + instance, + *, + use_integers_for_enums=True, + including_default_value_fields=True + ) -> str: """Given a message instance, serialize it to json Args: @@ -343,7 +349,7 @@ def to_json(cls, instance, *, use_integers_for_enums=True) -> str: return MessageToJson( cls.pb(instance), use_integers_for_enums=use_integers_for_enums, - including_default_value_fields=True, + including_default_value_fields=including_default_value_fields, ) def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index 34c2d438a65a..4b7be8be96c9 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -97,6 +97,29 @@ class Zone(proto.Enum): assert json2 == '{"zone":"EPIPELAGIC"}' +def test_json_default_values(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + name = proto.Field(proto.STRING, number=2) + + s = Squid(name="Steve") + json1 = ( + Squid.to_json(s, including_default_value_fields=False) + .replace(" ", "") + .replace("\n", "") + ) + assert json1 == '{"name":"Steve"}' + + json2 = Squid.to_json(s).replace(" ", "").replace("\n", "") + assert ( + json2 == '{"name":"Steve","massKg":0}' or json2 == '{"massKg":0,"name":"Steve"}' + ) + + s1 = Squid.from_json(json1) + s2 = Squid.from_json(json2) + assert s == s1 == s2 + + def test_json_unknown_field(): # Note that 'lengthCm' is unknown in the local definition. # This could happen if the client is using an older proto definition From 80193111508a193c4b37884bf13a7344f3c7ad20 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sat, 21 Nov 2020 01:38:03 +0000 Subject: [PATCH 097/272] chore: release 1.12.0 (#165) :robot: I have created a release \*beep\* \*boop\* --- ## [1.12.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.11.0...v1.12.0) (2020-11-20) ### Features * add default values parameter to to_json ([#164](https://www.github.com/googleapis/proto-plus-python/issues/164)) ([691f1b2](https://www.github.com/googleapis/proto-plus-python/commit/691f1b24454502c4ac49a88a09d1c9fbc287b2bd)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index fccdae02ced8..b612b8392a26 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.12.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.11.0...v1.12.0) (2020-11-20) + + +### Features + +* add default values parameter to to_json ([#164](https://www.github.com/googleapis/proto-plus-python/issues/164)) ([691f1b2](https://www.github.com/googleapis/proto-plus-python/commit/691f1b24454502c4ac49a88a09d1c9fbc287b2bd)) + ## [1.11.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.2...v1.11.0) (2020-10-19) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index fb5c7d4246a0..1d17e85d1d8b 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.11.0" +version = "1.12.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From d0c7c19edbfc1a8a8f8b9eb141b20f05981612bc Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 4 Dec 2020 14:33:13 -0700 Subject: [PATCH 098/272] feat: add 3.9 support and drop 3.5 (#167) - Drop 3.5 which is now EOL. https://www.python.org/downloads/release/python-3510/ - Add 3.9 tests and make them required. (Skipping 3.9 C++ tests as wheels are not yet published) - Add a constraints file and use it to test lower bounds in the setup.py in one unit test session (3.6) --- packages/proto-plus/.circleci/config.yml | 67 ++++++++++++------- .../.github/sync-repo-settings.yaml | 3 +- packages/proto-plus/noxfile.py | 17 ++++- packages/proto-plus/setup.py | 3 +- .../proto-plus/testing/constraints-3.10.txt | 0 .../proto-plus/testing/constraints-3.11.txt | 0 .../proto-plus/testing/constraints-3.6.txt | 9 +++ .../proto-plus/testing/constraints-3.7.txt | 0 .../proto-plus/testing/constraints-3.8.txt | 0 .../proto-plus/testing/constraints-3.9.txt | 0 10 files changed, 68 insertions(+), 31 deletions(-) create mode 100644 packages/proto-plus/testing/constraints-3.10.txt create mode 100644 packages/proto-plus/testing/constraints-3.11.txt create mode 100644 packages/proto-plus/testing/constraints-3.6.txt create mode 100644 packages/proto-plus/testing/constraints-3.7.txt create mode 100644 packages/proto-plus/testing/constraints-3.8.txt create mode 100644 packages/proto-plus/testing/constraints-3.9.txt diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml index 2df0719c6979..bb0aa10ef1fa 100644 --- a/packages/proto-plus/.circleci/config.yml +++ b/packages/proto-plus/.circleci/config.yml @@ -16,14 +16,6 @@ workflows: ignore: /.*/ tags: only: /^v\d+\.\d+\.\d+.*/ - - unit-3.5: - filters: - tags: - only: /.*/ - - unit-cpp-3.5: - filters: - tags: - only: /.*/ - unit-3.6: filters: tags: @@ -48,20 +40,28 @@ workflows: filters: tags: only: /.*/ + - unit-3.9: + filters: + tags: + only: /.*/ + # - unit-cpp-3.9: + # filters: + # tags: + # only: /.*/ - docs: filters: tags: only: /.*/ - publish: requires: - - unit-3.5 - - unit-cpp-3.5 - unit-3.6 - unit-cpp-3.6 - unit-3.7 - unit-cpp-3.7 - unit-3.8 - unit-cpp-3.8 + - unit-3.9 + # - unit-cpp-3.9 - docs filters: branches: @@ -69,9 +69,9 @@ workflows: tags: only: /^\d+\.\d+\.\d+((a|b|rc)\d+)?$/ jobs: - unit-3.5: + unit-3.6: docker: - - image: python:3.5 + - image: python:3.6 steps: - checkout - run: @@ -81,14 +81,14 @@ jobs: pip install codecov - run: name: Run unit tests. - command: nox -s unit-3.5 + command: nox -s unit-3.6 - run: name: Submit coverage data to codecov. command: codecov when: always - unit-3.6: + unit-3.7: docker: - - image: python:3.6 + - image: python:3.7 steps: - checkout - run: @@ -98,14 +98,14 @@ jobs: pip install codecov - run: name: Run unit tests. - command: nox -s unit-3.6 + command: nox -s unit-3.7 - run: name: Submit coverage data to codecov. command: codecov when: always - unit-3.7: + unit-3.8: docker: - - image: python:3.7 + - image: python:3.8 steps: - checkout - run: @@ -115,14 +115,14 @@ jobs: pip install codecov - run: name: Run unit tests. - command: nox -s unit-3.7 + command: nox -s unit-3.8 - run: name: Submit coverage data to codecov. command: codecov when: always - unit-3.8: + unit-3.9: docker: - - image: python:3.8 + - image: python:3.9 steps: - checkout - run: @@ -132,14 +132,14 @@ jobs: pip install codecov - run: name: Run unit tests. - command: nox -s unit-3.8 + command: nox -s unit-3.9 - run: name: Submit coverage data to codecov. command: codecov when: always - unit-cpp-3.5: + unit-3.10: docker: - - image: python:3.5 + - image: python:3.10-rc steps: - checkout - run: @@ -149,7 +149,7 @@ jobs: pip install codecov - run: name: Run unit tests. - command: nox -s unitcpp-3.5 + command: nox -s unit-3.10 - run: name: Submit coverage data to codecov. command: codecov @@ -205,6 +205,23 @@ jobs: name: Submit coverage data to codecov. command: codecov when: always + # unit-cpp-3.9: + # docker: + # - image: python:3.9 + # steps: + # - checkout + # - run: + # name: Install nox and codecov. + # command: | + # pip install nox + # pip install codecov + # - run: + # name: Run unit tests. + # command: nox -s unitcpp-3.9 + # - run: + # name: Submit coverage data to codecov. + # command: codecov + # when: always docs: docker: - image: python:3.6 diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index 9d1600c80a12..fec40052a299 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -7,11 +7,10 @@ branchProtectionRules: requiredStatusCheckContexts: - 'ci/circleci: docs' - 'ci/circleci: style-check' - - 'ci/circleci: unit-3.5' - 'ci/circleci: unit-3.6' - 'ci/circleci: unit-3.7' - 'ci/circleci: unit-3.8' - - 'ci/circleci: unit-cpp-3.5' + - 'ci/circleci: unit-3.9' - 'ci/circleci: unit-cpp-3.6' - 'ci/circleci: unit-cpp-3.7' - 'ci/circleci: unit-cpp-3.8' diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 3a2fff281150..0ed6caa7178a 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -14,17 +14,25 @@ from __future__ import absolute_import import os +import pathlib import nox -@nox.session(python=["3.5", "3.6", "3.7", "3.8"]) +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + + +@nox.session(python=["3.6", "3.7", "3.8", "3.9"]) def unit(session, proto="python"): """Run the unit test suite.""" + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto session.install("coverage", "pytest", "pytest-cov", "pytz") - session.install("-e", ".[testing]") + session.install("-e", ".[testing]", "-c", constraints_path) session.run( "py.test", @@ -43,7 +51,10 @@ def unit(session, proto="python"): ) -@nox.session(python=["3.5", "3.6", "3.7", "3.8"]) +# 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"]) def unitcpp(session): return unit(session, proto="cpp") diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 1d17e85d1d8b..83bd76f9fd80 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -38,6 +38,7 @@ include_package_data=True, install_requires=("protobuf >= 3.12.0",), extras_require={"testing": ["google-api-core[grpc] >= 1.22.2",],}, + python_requires=">=3.6", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -45,10 +46,10 @@ "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", ], diff --git a/packages/proto-plus/testing/constraints-3.10.txt b/packages/proto-plus/testing/constraints-3.10.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/proto-plus/testing/constraints-3.11.txt b/packages/proto-plus/testing/constraints-3.11.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/proto-plus/testing/constraints-3.6.txt b/packages/proto-plus/testing/constraints-3.6.txt new file mode 100644 index 000000000000..f7365f65e449 --- /dev/null +++ b/packages/proto-plus/testing/constraints-3.6.txt @@ -0,0 +1,9 @@ +# 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 +protobuf==3.12.0 +google-api-core==1.22.2 \ No newline at end of file diff --git a/packages/proto-plus/testing/constraints-3.7.txt b/packages/proto-plus/testing/constraints-3.7.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/proto-plus/testing/constraints-3.8.txt b/packages/proto-plus/testing/constraints-3.8.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/proto-plus/testing/constraints-3.9.txt b/packages/proto-plus/testing/constraints-3.9.txt new file mode 100644 index 000000000000..e69de29bb2d1 From 5d92c26980fdc5e098efaa05ebef4a916c9be162 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 7 Dec 2020 17:56:04 +0000 Subject: [PATCH 099/272] chore: release 1.13.0 (#168) :robot: I have created a release \*beep\* \*boop\* --- ## [1.13.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.12.0...v1.13.0) (2020-12-04) ### Features * add 3.9 support and drop 3.5 ([#167](https://www.github.com/googleapis/proto-plus-python/issues/167)) ([6d17195](https://www.github.com/googleapis/proto-plus-python/commit/6d171956e14b398aece931b9dde1013be9644b74)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index b612b8392a26..33c737f468b8 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.13.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.12.0...v1.13.0) (2020-12-04) + + +### Features + +* add 3.9 support and drop 3.5 ([#167](https://www.github.com/googleapis/proto-plus-python/issues/167)) ([6d17195](https://www.github.com/googleapis/proto-plus-python/commit/6d171956e14b398aece931b9dde1013be9644b74)) + ## [1.12.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.11.0...v1.12.0) (2020-11-20) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 83bd76f9fd80..4b6308a2595d 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.12.0" +version = "1.13.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From f798e73458b8f60c43e4061329bc44eb0c503532 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 5 Jan 2021 10:37:51 -0800 Subject: [PATCH 100/272] refactor: prefer super().__setattr__ over __dict__ manipulation (#174) Using super().__setattr__ is a preferred idiom over directly manipulating an object's __dict__. This allows for a more seamless transition to a named tuple subclass, or __slots__. --- packages/proto-plus/proto/message.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 28a87a806a8f..abb8f63591ca 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -300,7 +300,7 @@ def wrap(cls, pb): """ # Optimized fast path. instance = cls.__new__(cls) - instance.__dict__["_pb"] = pb + super(cls, instance).__setattr__("_pb", pb) return instance def serialize(cls, instance) -> bytes: @@ -414,7 +414,7 @@ def __init__(self, mapping=None, *, ignore_unknown_fields=False, **kwargs): if mapping is None: if not kwargs: # Special fast path for empty construction. - self.__dict__["_pb"] = self._meta.pb() + super().__setattr__("_pb", self._meta.pb()) return mapping = kwargs @@ -430,7 +430,7 @@ def __init__(self, mapping=None, *, ignore_unknown_fields=False, **kwargs): if kwargs: mapping.MergeFrom(self._meta.pb(**kwargs)) - self.__dict__["_pb"] = mapping + super().__setattr__("_pb", mapping) return elif isinstance(mapping, type(self)): # Just use the above logic on mapping's underlying pb. @@ -468,7 +468,7 @@ def __init__(self, mapping=None, *, ignore_unknown_fields=False, **kwargs): params[key] = pb_value # Create the internal protocol buffer. - self.__dict__["_pb"] = self._meta.pb(**params) + super().__setattr__("_pb", self._meta.pb(**params)) def __bool__(self): """Return True if any field is truthy, False otherwise.""" From 1a12a8ae74a614d2c0db64261ca056ed8b3211cc Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 9 Feb 2021 15:00:51 -0800 Subject: [PATCH 101/272] chore: Create tests.yml (#183) Convert from CircleCI to Github Actions --- packages/proto-plus/.circleci/config.yml | 288 ------------------ .../.github/sync-repo-settings.yaml | 12 +- .../proto-plus/.github/workflows/tests.yml | 61 ++++ 3 files changed, 64 insertions(+), 297 deletions(-) delete mode 100644 packages/proto-plus/.circleci/config.yml create mode 100644 packages/proto-plus/.github/workflows/tests.yml diff --git a/packages/proto-plus/.circleci/config.yml b/packages/proto-plus/.circleci/config.yml deleted file mode 100644 index bb0aa10ef1fa..000000000000 --- a/packages/proto-plus/.circleci/config.yml +++ /dev/null @@ -1,288 +0,0 @@ ---- -version: 2 -workflows: - version: 2 - tests: - jobs: - - style-check: - filters: - tags: - only: /^v\d+\.\d+\.\d+.*/ - - publish_package: - requires: - - style-check - filters: - branches: - ignore: /.*/ - tags: - only: /^v\d+\.\d+\.\d+.*/ - - unit-3.6: - filters: - tags: - only: /.*/ - - unit-cpp-3.6: - filters: - tags: - only: /.*/ - - unit-3.7: - filters: - tags: - only: /.*/ - - unit-cpp-3.7: - filters: - tags: - only: /.*/ - - unit-3.8: - filters: - tags: - only: /.*/ - - unit-cpp-3.8: - filters: - tags: - only: /.*/ - - unit-3.9: - filters: - tags: - only: /.*/ - # - unit-cpp-3.9: - # filters: - # tags: - # only: /.*/ - - docs: - filters: - tags: - only: /.*/ - - publish: - requires: - - unit-3.6 - - unit-cpp-3.6 - - unit-3.7 - - unit-cpp-3.7 - - unit-3.8 - - unit-cpp-3.8 - - unit-3.9 - # - unit-cpp-3.9 - - docs - filters: - branches: - ignore: /.*/ - tags: - only: /^\d+\.\d+\.\d+((a|b|rc)\d+)?$/ -jobs: - unit-3.6: - docker: - - image: python:3.6 - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unit-3.6 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - unit-3.7: - docker: - - image: python:3.7 - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unit-3.7 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - unit-3.8: - docker: - - image: python:3.8 - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unit-3.8 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - unit-3.9: - docker: - - image: python:3.9 - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unit-3.9 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - unit-3.10: - docker: - - image: python:3.10-rc - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unit-3.10 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - unit-cpp-3.6: - docker: - - image: python:3.6 - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unitcpp-3.6 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - unit-cpp-3.7: - docker: - - image: python:3.7 - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unitcpp-3.7 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - unit-cpp-3.8: - docker: - - image: python:3.8 - steps: - - checkout - - run: - name: Install nox and codecov. - command: | - pip install nox - pip install codecov - - run: - name: Run unit tests. - command: nox -s unitcpp-3.8 - - run: - name: Submit coverage data to codecov. - command: codecov - when: always - # unit-cpp-3.9: - # docker: - # - image: python:3.9 - # steps: - # - checkout - # - run: - # name: Install nox and codecov. - # command: | - # pip install nox - # pip install codecov - # - run: - # name: Run unit tests. - # command: nox -s unitcpp-3.9 - # - run: - # name: Submit coverage data to codecov. - # command: codecov - # when: always - docs: - docker: - - image: python:3.6 - steps: - - checkout - - run: - name: Install nox. - command: pip install nox - - run: - name: Build the documentation. - command: nox -s docs - publish: - docker: - - image: python:3.7 - steps: - - checkout - - run: - name: Install twine - command: | - pip install twine - - run: - name: Build the package. - command: python setup.py sdist - - run: - name: Publish to PyPI. - command: twine upload dist/* - - style-check: - docker: - - image: python:3.8-slim - steps: - - checkout - - run: - name: Install git - command: | - apt-get update - apt-get install -y git - - run: - name: Install black - command: | - pip install black==19.10b0 - - run: - name: Format files - command: | - black . - - run: - name: Check diff - command: | - git diff --ignore-submodules=all --color --exit-code . - publish_package: - docker: - - image: python:3.8-slim - steps: - - checkout - - run: - name: Install twine - command: | - pip install twine - - run: - name: Build the package. - command: python setup.py sdist - - run: - name: Publish to PyPI. - command: twine upload dist/* diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index fec40052a299..a1cc91e12f2f 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -5,15 +5,9 @@ branchProtectionRules: - pattern: master isAdminEnforced: true requiredStatusCheckContexts: - - 'ci/circleci: docs' - - 'ci/circleci: style-check' - - 'ci/circleci: unit-3.6' - - 'ci/circleci: unit-3.7' - - 'ci/circleci: unit-3.8' - - 'ci/circleci: unit-3.9' - - 'ci/circleci: unit-cpp-3.6' - - 'ci/circleci: unit-cpp-3.7' - - 'ci/circleci: unit-cpp-3.8' + - 'style-check' + - 'docs' + - 'unit' - 'cla/google' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml new file mode 100644 index 000000000000..3be96ea023a3 --- /dev/null +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -0,0 +1,61 @@ +name: Tests + +# Controls when the action will run. +on: + pull_request: + push: + branches: [ $default-branch ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + style-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install black + run: pip install black==19.10b0 + - name: Check diff + run: black --diff --check . + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install nox. + run: python -m pip install nox + - name: Build the documentation. + run: nox -s docs + unit: + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9] + variant: ['', cpp] + # Note: as of 2021-02-09, there are no 3.9 python wheels for protobuf/grpc + exclude: + - python: 3.9 + variant: cpp + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install nox and codecov. + run: | + pip install nox + pip install codecov + - name: Run unit tests. + run: nox -s unit${{ matrix.variant }}-${{ matrix.python }} + - name: Submit coverage data to codecov. + run: codecov + if: always() From d45732e5caa68c1fa7f43857ae187a3f13d89ceb Mon Sep 17 00:00:00 2001 From: Helio Machado <0x2b3bfa0+git@googlemail.com> Date: Wed, 10 Feb 2021 00:35:58 +0100 Subject: [PATCH 102/272] fix: update docstring to match type hint (#172) * fix: update docstring to match type hint * proto/modules.py * Update modules.py Co-authored-by: Dov Shlachter --- packages/proto-plus/proto/modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/proto/modules.py b/packages/proto-plus/proto/modules.py index 224213f40d6c..9d2e519e0563 100644 --- a/packages/proto-plus/proto/modules.py +++ b/packages/proto-plus/proto/modules.py @@ -33,7 +33,7 @@ def define_module( package (str): The proto package name. marshal (str): The name of the marshal to use. It is recommended to use one marshal per Python library (e.g. package on PyPI). - manifest (Tuple[str]): A tuple of classes to be created. Setting + manifest (Set[str]): A set of messages and enums to be created. Setting this adds a slight efficiency in piecing together proto descriptors under the hood. """ From a263d46c491d6b3d26e4c45f573cb66302748955 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 9 Feb 2021 23:44:04 +0000 Subject: [PATCH 103/272] chore: release 1.13.1 (#184) :robot: I have created a release \*beep\* \*boop\* --- ### [1.13.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.13.0...v1.13.1) (2021-02-09) ### Bug Fixes * update docstring to match type hint ([#172](https://www.github.com/googleapis/proto-plus-python/issues/172)) ([14dad5b](https://www.github.com/googleapis/proto-plus-python/commit/14dad5bf6c5967a720e9d3095d621dbfe208b614)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 33c737f468b8..00a9e65d0012 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.13.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.13.0...v1.13.1) (2021-02-09) + + +### Bug Fixes + +* update docstring to match type hint ([#172](https://www.github.com/googleapis/proto-plus-python/issues/172)) ([14dad5b](https://www.github.com/googleapis/proto-plus-python/commit/14dad5bf6c5967a720e9d3095d621dbfe208b614)) + ## [1.13.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.12.0...v1.13.0) (2020-12-04) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 4b6308a2595d..fd2a81a1652f 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.13.0" +version = "1.13.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 8daee55288cf9802d0220ccee110c978800d13be Mon Sep 17 00:00:00 2001 From: Helio Machado <0x2b3bfa0+git@googlemail.com> Date: Wed, 10 Feb 2021 23:02:40 +0100 Subject: [PATCH 104/272] fix: proper native marshal for repeated enumeration fields (#180) Fix for #179 . Repeated Enumerations now show the correct type when interacted with via python. --- .../proto-plus/proto/marshal/collections/repeated.py | 10 ++++++++-- packages/proto-plus/proto/marshal/marshal.py | 5 ++++- .../tests/test_fields_repeated_composite.py | 12 ++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index 3063e546b5e7..1453976f2ab5 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -25,7 +25,7 @@ class Repeated(collections.abc.MutableSequence): modify the underlying field container directly. """ - def __init__(self, sequence, *, marshal): + def __init__(self, sequence, *, marshal, proto_type=None): """Initialize a wrapper around a protobuf repeated field. Args: @@ -35,6 +35,7 @@ def __init__(self, sequence, *, marshal): """ self._pb = sequence self._marshal = marshal + self._proto_type = proto_type def __copy__(self): """Copy this object and return the copy.""" @@ -61,7 +62,7 @@ def __ne__(self, other): return not self == other def __repr__(self): - return repr(self.pb) + return repr([*self]) def __setitem__(self, key, value): self.pb[key] = value @@ -89,6 +90,11 @@ class RepeatedComposite(Repeated): @cached_property def _pb_type(self): """Return the protocol buffer type for this sequence.""" + # Provide the marshal-given proto_type, if any. + # Used for RepeatedComposite of Enum. + if self._proto_type is not None: + return self._proto_type + # There is no public-interface mechanism to determine the type # of what should go in the list (and the C implementation seems to # have no exposed mechanism at all). diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index f304cefc7f63..c8224ce25038 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -157,7 +157,10 @@ def to_python(self, proto_type, value, *, absent: bool = None): if value_type in compat.repeated_composite_types: return RepeatedComposite(value, marshal=self) if value_type in compat.repeated_scalar_types: - return Repeated(value, marshal=self) + if isinstance(proto_type, type): + return RepeatedComposite(value, marshal=self, proto_type=proto_type) + else: + return Repeated(value, marshal=self) # Same thing for maps of messages. if value_type in compat.map_composite_types: diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index 2990eea6597b..75cb7a4d205f 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -14,6 +14,7 @@ from datetime import datetime from datetime import timezone +from enum import Enum import pytest @@ -95,6 +96,17 @@ class Foo(proto.Message): assert foo.timestamps[2].hour == 0 +def test_repeated_composite_enum(): + class Foo(proto.Message): + class Bar(proto.Enum): + BAZ = 0 + + bars = proto.RepeatedField(Bar, number=1) + + foo = Foo(bars=[Foo.Bar.BAZ]) + assert isinstance(foo.bars[0], Enum) + + def test_repeated_composite_outer_write(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) From 8a8e616564e29f65c7b0a9eca05e3978a657befa Mon Sep 17 00:00:00 2001 From: Helio Machado <0x2b3bfa0+git@googlemail.com> Date: Wed, 10 Feb 2021 23:59:40 +0100 Subject: [PATCH 105/272] fix: proper __setitem__ and insert for RepeatedComposite (#178) Fixes #177; please refer to the issue for context. The proposed solution is not elegant at all, but might be the best in the short term until the underlying protocol buffer object supports the __setitem__ method. Nota bene: the underlying RepeatedCompositeFieldContainer from google.protobuf.internal.containers already supports the insert method, and we only need to perform type conversion for this particular case. --- .../proto/marshal/collections/repeated.py | 68 ++++++++++--- .../tests/test_fields_repeated_composite.py | 96 ++++++++++++++++++- 2 files changed, 147 insertions(+), 17 deletions(-) diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index 1453976f2ab5..30fa68d08a20 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -119,22 +119,60 @@ def __getitem__(self, key): return self._marshal.to_python(self._pb_type, self.pb[key]) def __setitem__(self, key, value): - pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) - - # Protocol buffers does not define a useful __setitem__, so we - # have to pop everything after this point off the list and reload it. - after = [pb_value] - while self.pb[key:]: - after.append(self.pb.pop(key)) - self.pb.extend(after) + # The underlying protocol buffer does not define __setitem__, so we + # have to implement all the operations on our own. + + # If ``key`` is an integer, as in list[index] = value: + if isinstance(key, int): + if -len(self) <= key < len(self): + self.pop(key) # Delete the old item. + self.insert(key, value) # Insert the new item in its place. + else: + raise IndexError("list assignment index out of range") + + # If ``key`` is a slice object, as in list[start:stop:step] = [values]: + elif isinstance(key, slice): + start, stop, step = key.indices(len(self)) + + if not isinstance(value, collections.abc.Iterable): + raise TypeError("can only assign an iterable") + + if step == 1: # Is not an extended slice. + # Assign all the new values to the sliced part, replacing the + # old values, if any, and unconditionally inserting those + # values whose indices already exceed the slice length. + for index, item in enumerate(value): + if start + index < stop: + self.pop(start + index) + self.insert(start + index, item) + + # If there are less values than the length of the slice, remove + # the remaining elements so that the slice adapts to the + # newly provided values. + for _ in range(stop - start - len(value)): + self.pop(start + len(value)) + + else: # Is an extended slice. + indices = range(start, stop, step) + + if len(value) != len(indices): # XXX: Use PEP 572 on 3.8+ + raise ValueError( + f"attempt to assign sequence of size " + f"{len(value)} to extended slice of size " + f"{len(indices)}" + ) + + # Assign each value to its index, calling this function again + # with individual integer indexes that get processed above. + for index, item in zip(indices, value): + self[index] = item + + else: + raise TypeError( + f"list indices must be integers or slices, not {type(key).__name__}" + ) def insert(self, index: int, value): """Insert ``value`` in the sequence before ``index``.""" pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) - - # Protocol buffers does not define a useful insert, so we have - # to pop everything after this point off the list and reload it. - after = [pb_value] - while self.pb[index:]: - after.append(self.pb.pop(index)) - self.pb.extend(after) + self.pb.insert(index, pb_value) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index 75cb7a4d205f..2ce691efdd47 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -165,7 +165,7 @@ class Baz(proto.Message): assert baz.foos[1].bar == 48 -def test_repeated_composite_set(): +def test_repeated_composite_set_index(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) @@ -176,9 +176,101 @@ class Baz(proto.Message): baz.foos[1] = Foo(bar=55) assert baz.foos[0].bar == 96 assert baz.foos[1].bar == 55 + assert len(baz.foos) == 2 + + +def test_repeated_composite_set_index_error(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[]) + with pytest.raises(IndexError): + baz.foos[0] = Foo(bar=55) + + +def test_repeated_composite_set_slice_less(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[{"bar": 96}, {"bar": 48}, {"bar": 24}]) + baz.foos[:2] = [{"bar": 12}] + assert baz.foos[0].bar == 12 + assert baz.foos[1].bar == 24 + assert len(baz.foos) == 2 + + +def test_repeated_composite_set_slice_more(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[{"bar": 12}]) + baz.foos[:2] = [{"bar": 96}, {"bar": 48}, {"bar": 24}] + assert baz.foos[0].bar == 96 + assert baz.foos[1].bar == 48 + assert baz.foos[2].bar == 24 + assert len(baz.foos) == 3 + + +def test_repeated_composite_set_slice_not_iterable(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[]) + with pytest.raises(TypeError): + baz.foos[:1] = None + + +def test_repeated_composite_set_extended_slice(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[{"bar": 96}, {"bar": 48}]) + baz.foos[::-1] = [{"bar": 96}, {"bar": 48}] + assert baz.foos[0].bar == 48 + assert baz.foos[1].bar == 96 + assert len(baz.foos) == 2 + + +def test_repeated_composite_set_extended_slice_wrong_length(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[{"bar": 96}]) + with pytest.raises(ValueError): + baz.foos[::-1] = [] + + +def test_repeated_composite_set_wrong_key_type(): + class Foo(proto.Message): + bar = proto.Field(proto.INT32, number=1) + + class Baz(proto.Message): + foos = proto.RepeatedField(proto.MESSAGE, message=Foo, number=1) + + baz = Baz(foos=[]) + with pytest.raises(TypeError): + baz.foos[None] = Foo(bar=55) -def test_repeated_composite_set_wrong_type(): +def test_repeated_composite_set_wrong_value_type(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) From ba4b022247ba86d07148ec610146222ff32f5bb7 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 19 Feb 2021 10:27:44 -0800 Subject: [PATCH 106/272] feat: Pypi publish ghub actions (#189) --- .../.github/workflows/pypi-upload.yaml | 24 +++++++++++++++++++ .../.github/workflows/release-please.yml | 16 +++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 packages/proto-plus/.github/workflows/pypi-upload.yaml create mode 100644 packages/proto-plus/.github/workflows/release-please.yml diff --git a/packages/proto-plus/.github/workflows/pypi-upload.yaml b/packages/proto-plus/.github/workflows/pypi-upload.yaml new file mode 100644 index 000000000000..28ae777eae24 --- /dev/null +++ b/packages/proto-plus/.github/workflows/pypi-upload.yaml @@ -0,0 +1,24 @@ +name: Upload Python Package to PyPI + +on: + release: + types: [created] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: python -m pip install twine + - name: Package and upload modulee + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/packages/proto-plus/.github/workflows/release-please.yml b/packages/proto-plus/.github/workflows/release-please.yml new file mode 100644 index 000000000000..f62dda84b9c0 --- /dev/null +++ b/packages/proto-plus/.github/workflows/release-please.yml @@ -0,0 +1,16 @@ +name: release-please + +on: + push: + branches: + - master + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: GoogleCloudPlatform/release-please-action@v1.3.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release-type: python + package-name: proto-plus From 0fb3574b55b90e5a52c660e05d0369763680f5a4 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 19 Feb 2021 10:36:50 -0800 Subject: [PATCH 107/272] chore: Ci fix (#187) --- packages/proto-plus/.github/workflows/tests.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 3be96ea023a3..f0642c8b1bb2 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -13,6 +13,10 @@ jobs: style-check: runs-on: ubuntu-latest steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.7.0 + with: + access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v2 @@ -25,6 +29,10 @@ jobs: docs: runs-on: ubuntu-latest steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.7.0 + with: + access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v2 @@ -45,6 +53,10 @@ jobs: - python: 3.9 variant: cpp steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.7.0 + with: + access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v2 From d6a830ee7daec48ceb71996201004d83c8559085 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 24 Feb 2021 14:18:41 -0800 Subject: [PATCH 108/272] chore: attempt to remove release-please Github action (#196) It's possible the .github/release-please.yml file is sufficient. --- .../.github/workflows/release-please.yml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 packages/proto-plus/.github/workflows/release-please.yml diff --git a/packages/proto-plus/.github/workflows/release-please.yml b/packages/proto-plus/.github/workflows/release-please.yml deleted file mode 100644 index f62dda84b9c0..000000000000 --- a/packages/proto-plus/.github/workflows/release-please.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: release-please - -on: - push: - branches: - - master - -jobs: - release-please: - runs-on: ubuntu-latest - steps: - - uses: GoogleCloudPlatform/release-please-action@v1.3.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - release-type: python - package-name: proto-plus From 9415444aaa2897b5ec01ed58705930f2bc973aba Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 24 Feb 2021 23:26:19 +0100 Subject: [PATCH 109/272] chore(deps): update styfle/cancel-workflow-action action to v0.8.0 (#193) Co-authored-by: Dov Shlachter --- packages/proto-plus/.github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index f0642c8b1bb2..b64a74626f3f 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.7.0 + uses: styfle/cancel-workflow-action@0.8.0 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.7.0 + uses: styfle/cancel-workflow-action@0.8.0 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 @@ -54,7 +54,7 @@ jobs: variant: cpp steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.7.0 + uses: styfle/cancel-workflow-action@0.8.0 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 From 8fe16bbd32dcd458c26548c537e207a55e27965c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 24 Feb 2021 15:25:58 -0800 Subject: [PATCH 110/272] chore: release 1.14.0 (#191) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Dov Shlachter --- packages/proto-plus/CHANGELOG.md | 13 +++++++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 00a9e65d0012..7cb13f4b8a2f 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.14.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.13.1...v1.14.0) (2021-02-24) + + +### Features + +* Pypi publish ghub actions ([#189](https://www.github.com/googleapis/proto-plus-python/issues/189)) ([4c967b0](https://www.github.com/googleapis/proto-plus-python/commit/4c967b0bb2ead29156bcc53c1f3b227b3afb2e8b)) + + +### Bug Fixes + +* proper __setitem__ and insert for RepeatedComposite ([#178](https://www.github.com/googleapis/proto-plus-python/issues/178)) ([1157a76](https://www.github.com/googleapis/proto-plus-python/commit/1157a76bb608d72389f46dc4d8e9aa00cc14ccc6)) +* proper native marshal for repeated enumeration fields ([#180](https://www.github.com/googleapis/proto-plus-python/issues/180)) ([30265d6](https://www.github.com/googleapis/proto-plus-python/commit/30265d654d7f3589cbd0994d2ac564db1fd44265)) + ### [1.13.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.13.0...v1.13.1) (2021-02-09) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index fd2a81a1652f..09b780c143dc 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.13.1" +version = "1.14.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From cafc781d29f33dd705a25b226d4847bb1a7e6f73 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 26 Feb 2021 09:52:23 -0800 Subject: [PATCH 111/272] fix: install the wheel dependency (#197) --- packages/proto-plus/.github/workflows/pypi-upload.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.github/workflows/pypi-upload.yaml b/packages/proto-plus/.github/workflows/pypi-upload.yaml index 28ae777eae24..055453598572 100644 --- a/packages/proto-plus/.github/workflows/pypi-upload.yaml +++ b/packages/proto-plus/.github/workflows/pypi-upload.yaml @@ -14,7 +14,7 @@ jobs: with: python-version: '3.x' - name: Install dependencies - run: python -m pip install twine + run: python -m pip install twine wheel - name: Package and upload modulee env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} From cca516c9af7a3e6d5019b9d86f258dd751ab5d1d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:56:04 -0800 Subject: [PATCH 112/272] chore: release 1.14.1 (#198) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 7cb13f4b8a2f..918571f49b2e 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.14.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.0...v1.14.1) (2021-02-26) + + +### Bug Fixes + +* install the wheel dependency ([#197](https://www.github.com/googleapis/proto-plus-python/issues/197)) ([923ab31](https://www.github.com/googleapis/proto-plus-python/commit/923ab31e4685b47acae793198be55335e5eeae38)) + ## [1.14.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.13.1...v1.14.0) (2021-02-24) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 09b780c143dc..447da7b723e6 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.14.0" +version = "1.14.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From b3f6ab6d287777c75ccfbe2bbe72c8b980c444c4 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 26 Feb 2021 15:23:46 -0800 Subject: [PATCH 113/272] fix: use the correct environment for uploading to pypi (#199) --- packages/proto-plus/.github/workflows/pypi-upload.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/proto-plus/.github/workflows/pypi-upload.yaml b/packages/proto-plus/.github/workflows/pypi-upload.yaml index 055453598572..60e1bd3c82e5 100644 --- a/packages/proto-plus/.github/workflows/pypi-upload.yaml +++ b/packages/proto-plus/.github/workflows/pypi-upload.yaml @@ -7,6 +7,7 @@ on: jobs: publish: runs-on: ubuntu-latest + environment: PyPI steps: - uses: actions/checkout@v2 - name: Set up Python From daad511805f46daa24d28e0ee4dc4d179bfbcdcd Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 26 Feb 2021 15:25:22 -0800 Subject: [PATCH 114/272] chore: release 1.14.2 (#200) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 918571f49b2e..20d712c96d23 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.14.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.1...v1.14.2) (2021-02-26) + + +### Bug Fixes + +* use the correct environment for uploading to pypi ([#199](https://www.github.com/googleapis/proto-plus-python/issues/199)) ([babdc5c](https://www.github.com/googleapis/proto-plus-python/commit/babdc5cddf08235cac3cda66200babab44204688)) + ### [1.14.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.0...v1.14.1) (2021-02-26) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 447da7b723e6..813adb9d67fc 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.14.1" +version = "1.14.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From e912b456d01faace20a24f433176f51352b8a59f Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 3 Mar 2021 17:00:12 -0800 Subject: [PATCH 115/272] fix: adding enums to a repeated field does not raise a TypeError (#202) Fixes issue #201, where enums added to a repeated field triggered a TypeError because they were coverted to integers during marshaling. --- .../proto/marshal/collections/repeated.py | 2 +- .../proto-plus/tests/test_marshal_strict.py | 24 ++++++++++++++ .../tests/test_marshal_types_enum.py | 32 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 packages/proto-plus/tests/test_marshal_strict.py diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index 30fa68d08a20..01b5d2fd9873 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -174,5 +174,5 @@ def __setitem__(self, key, value): def insert(self, index: int, value): """Insert ``value`` in the sequence before ``index``.""" - pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) + pb_value = self._marshal.to_proto(self._pb_type, value) self.pb.insert(index, pb_value) diff --git a/packages/proto-plus/tests/test_marshal_strict.py b/packages/proto-plus/tests/test_marshal_strict.py new file mode 100644 index 000000000000..75e2f05de90b --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_strict.py @@ -0,0 +1,24 @@ +# Copyright (C) 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto +from proto.marshal.marshal import BaseMarshal +import pytest + + +def test_strict_to_proto(): + m = BaseMarshal() + + with pytest.raises(TypeError): + m.to_proto(dict, None, strict=True) diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py index 1d302053b3f5..6cd348c37a53 100644 --- a/packages/proto-plus/tests/test_marshal_types_enum.py +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -58,3 +58,35 @@ class Foo(proto.Enum): with mock.patch.object(warnings, "warn") as warn: assert enum_rule.to_python(4) == 4 warn.assert_called_once_with("Unrecognized Foo enum value: 4") + + +def test_enum_append(): + class Bivalve(proto.Enum): + CLAM = 0 + OYSTER = 1 + + class MolluscContainer(proto.Message): + bivalves = proto.RepeatedField(proto.ENUM, number=1, enum=Bivalve,) + + mc = MolluscContainer() + clam = Bivalve.CLAM + mc.bivalves.append(clam) + mc.bivalves.append(1) + + assert mc.bivalves == [clam, Bivalve.OYSTER] + + +def test_enum_map_insert(): + class Bivalve(proto.Enum): + CLAM = 0 + OYSTER = 1 + + class MolluscContainer(proto.Message): + bivalves = proto.MapField(proto.STRING, proto.ENUM, number=1, enum=Bivalve,) + + mc = MolluscContainer() + clam = Bivalve.CLAM + mc.bivalves["clam"] = clam + mc.bivalves["oyster"] = 1 + + assert mc.bivalves == {"clam": clam, "oyster": Bivalve.OYSTER} From 54826a85cf8feb06abd38ec0809c9959601b1b27 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 4 Mar 2021 13:12:10 -0700 Subject: [PATCH 116/272] chore: release 1.14.3 (#203) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 20d712c96d23..9cbaddca5484 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.14.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.2...v1.14.3) (2021-03-04) + + +### Bug Fixes + +* adding enums to a repeated field does not raise a TypeError ([#202](https://www.github.com/googleapis/proto-plus-python/issues/202)) ([2a10bbe](https://www.github.com/googleapis/proto-plus-python/commit/2a10bbecaf8955c7bf1956086aef42630112788b)) + ### [1.14.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.1...v1.14.2) (2021-02-26) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 813adb9d67fc..f446e4b74185 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.14.2" +version = "1.14.3" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From b7c04b74670168b7190a9ebb65dad7be15cd79d4 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 4 Mar 2021 14:10:09 -0700 Subject: [PATCH 117/272] chore: edit required status checks (#204) Editing to match the names of the GH Actions checks --- packages/proto-plus/.github/sync-repo-settings.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index a1cc91e12f2f..a545722c6387 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -7,7 +7,13 @@ branchProtectionRules: requiredStatusCheckContexts: - 'style-check' - 'docs' - - 'unit' + - 'unit (3.6)' + - 'unit (3.6, cpp)' + - 'unit (3.7)' + - 'unit (3.7, cpp)' + - 'unit (3.8)' + - 'unit (3.9, cpp)' + - 'unit (3.9)' - 'cla/google' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true From cb5ed11f07c276e89c3519e23b2832790e098ec5 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 10 Mar 2021 11:44:04 -0700 Subject: [PATCH 118/272] chore: remove the circleci badge (#206) --- packages/proto-plus/README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 140248c05cb8..08ec2bd9f4f0 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -1,7 +1,7 @@ Proto Plus for Python ===================== -|pypi| |release level| |ci| |docs| |codecov| +|pypi| |release level| |docs| |codecov| Beautiful, Pythonic protocol buffers. @@ -26,7 +26,5 @@ Documentation :target: https://cloud.google.com/terms/launch-stages .. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest :target: https://proto-plus-python.readthedocs.io/en/latest/ -.. |ci| image:: https://circleci.com/gh/googleapis/proto-plus-python.svg?style=shield - :target: https://circleci.com/gh/googleapis/proto-plus-python .. |codecov| image:: https://codecov.io/gh/googleapis/proto-plus-python/graph/badge.svg :target: https://codecov.io/gh/googleapis/proto-plus-python From 01a95fbfb20ebc36f44c4be3c8d280e764b6b322 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 10 Mar 2021 13:34:32 -0800 Subject: [PATCH 119/272] feat: allow_alias for enums (#207) Certain APIs, e.g. recommendationengine, have enums where variants are aliased, i.e. different names map to the same integer value. Allowing this behavior in proto plus for the cpp protobuf runtime requires constructing and passing the correct options. --- .../.github/sync-repo-settings.yaml | 2 +- packages/proto-plus/proto/enums.py | 16 ++++++++ packages/proto-plus/tests/test_fields_enum.py | 39 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index a545722c6387..d5b332af5f45 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -12,7 +12,7 @@ branchProtectionRules: - 'unit (3.7)' - 'unit (3.7, cpp)' - 'unit (3.8)' - - 'unit (3.9, cpp)' + # - 'unit (3.9, cpp)' # Don't have binary wheels for 3.9 cpp protobuf yet - 'unit (3.9)' - 'cla/google' requiredApprovingReviewCount: 1 diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 22e8f42debb6..1fe8746f1ca9 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -47,6 +47,21 @@ def __new__(mcls, name, bases, attrs): filename = _file_info._FileInfo.proto_file_name( attrs.get("__module__", name.lower()) ) + + # Retrieve any enum options. + # We expect something that looks like an EnumOptions message, + # either an actual instance or a dict-like representation. + pb_options = "_pb_options" + opts = attrs.pop(pb_options, {}) + # This is the only portable way to remove the _pb_options name + # from the enum attrs. + # In 3.7 onwards, we can define an _ignore_ attribute and do some + # mucking around with that. + if pb_options in attrs._member_names: + idx = attrs._member_names.index(pb_options) + attrs._member_names.pop(idx) + + # Make the descriptor. enum_desc = descriptor_pb2.EnumDescriptorProto( name=name, # Note: the superclass ctor removes the variants, so get them now. @@ -60,6 +75,7 @@ def __new__(mcls, name, bases, attrs): ), key=lambda v: v.number, ), + options=opts, ) file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) diff --git a/packages/proto-plus/tests/test_fields_enum.py b/packages/proto-plus/tests/test_fields_enum.py index eeeab325c4a1..e5d5b324a285 100644 --- a/packages/proto-plus/tests/test_fields_enum.py +++ b/packages/proto-plus/tests/test_fields_enum.py @@ -12,7 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import proto +import pytest import sys @@ -353,3 +355,40 @@ class Task(proto.Message): t = Task(weekday="TUESDAY") t2 = Task.deserialize(Task.serialize(t)) assert t == t2 + + +if os.environ.get("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION", "python") == "cpp": + # This test only works, and is only relevant, with the cpp runtime. + # Python just doesn't give a care and lets it work anyway. + def test_enum_alias_bad(): + # Certain enums may shadow the different enum monikers with the same value. + # This is generally discouraged, and protobuf will object by default, + # but will explicitly allow this behavior if the enum is defined with + # the `allow_alias` option set. + with pytest.raises(TypeError): + + # The wrapper message is a hack to avoid manifest wrangling to + # define the enum. + class BadMessage(proto.Message): + class BadEnum(proto.Enum): + UNKNOWN = 0 + DEFAULT = 0 + + bad_dup_enum = proto.Field(proto.ENUM, number=1, enum=BadEnum) + + +def test_enum_alias_good(): + # Have to split good and bad enum alias into two tests so that the generated + # file descriptor is properly created. + # For the python runtime, aliases are allowed by default, but we want to + # make sure that the options don't cause problems. + # For the cpp runtime, we need to verify that we can in fact define aliases. + class GoodMessage(proto.Message): + class GoodEnum(proto.Enum): + _pb_options = {"allow_alias": True} + UNKNOWN = 0 + DEFAULT = 0 + + good_dup_enum = proto.Field(proto.ENUM, number=1, enum=GoodEnum) + + assert GoodMessage.GoodEnum.UNKNOWN == GoodMessage.GoodEnum.DEFAULT == 0 From 28c0fe83ef75d00c62fbd0ca65fdd8b6128be43c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 10 Mar 2021 13:38:41 -0800 Subject: [PATCH 120/272] chore: release 1.15.0 (#208) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 9cbaddca5484..42fb1ed7ba62 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.15.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.3...v1.15.0) (2021-03-10) + + +### Features + +* allow_alias for enums ([#207](https://www.github.com/googleapis/proto-plus-python/issues/207)) ([6d4d713](https://www.github.com/googleapis/proto-plus-python/commit/6d4d71399f494b9f3bd47b6f3ef0b6d3c0c547b5)) + ### [1.14.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.2...v1.14.3) (2021-03-04) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index f446e4b74185..b445ef9c24ca 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.14.3" +version = "1.15.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 3e52c15cc52b1e9849b2bd715e46915f4e584587 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 12 Mar 2021 10:08:16 -0800 Subject: [PATCH 121/272] feat: add preserving_proto_field_name passthrough in MessageMeta.to_dict (#211) --- packages/proto-plus/proto/message.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index abb8f63591ca..8ed2a69a9f45 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -369,7 +369,9 @@ def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": Parse(payload, instance._pb, ignore_unknown_fields=ignore_unknown_fields) return instance - def to_dict(cls, instance, *, use_integers_for_enums=True) -> "Message": + def to_dict( + cls, instance, *, use_integers_for_enums=True, preserving_proto_field_name=True + ) -> "Message": """Given a message instance, return its representation as a python dict. Args: @@ -378,6 +380,9 @@ def to_dict(cls, instance, *, use_integers_for_enums=True) -> "Message": 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. Returns: dict: A representation of the protocol buffer using pythonic data structures. @@ -387,7 +392,7 @@ def to_dict(cls, instance, *, use_integers_for_enums=True) -> "Message": return MessageToDict( cls.pb(instance), including_default_value_fields=True, - preserving_proto_field_name=True, + preserving_proto_field_name=preserving_proto_field_name, use_integers_for_enums=use_integers_for_enums, ) From 4868d0d058945211ef6a2cf08af851855db1946c Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 12 Mar 2021 10:19:56 -0800 Subject: [PATCH 122/272] chore: use repository secrets for pypi upload (#210) This is coupled with a behind the scenes change to the repository secrets. --- packages/proto-plus/.github/workflows/pypi-upload.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/proto-plus/.github/workflows/pypi-upload.yaml b/packages/proto-plus/.github/workflows/pypi-upload.yaml index 60e1bd3c82e5..055453598572 100644 --- a/packages/proto-plus/.github/workflows/pypi-upload.yaml +++ b/packages/proto-plus/.github/workflows/pypi-upload.yaml @@ -7,7 +7,6 @@ on: jobs: publish: runs-on: ubuntu-latest - environment: PyPI steps: - uses: actions/checkout@v2 - name: Set up Python From d3ba01561b4bd0e750015ba97cc0f50673e51717 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 12 Mar 2021 11:01:59 -0800 Subject: [PATCH 123/272] chore: release 1.16.0 (#212) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Dov Shlachter --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 42fb1ed7ba62..4db46b5f2ce2 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.16.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.15.0...v1.16.0) (2021-03-12) + + +### Features + +* add preserving_proto_field_name passthrough in MessageMeta.to_dict ([#211](https://www.github.com/googleapis/proto-plus-python/issues/211)) ([7675a0c](https://www.github.com/googleapis/proto-plus-python/commit/7675a0c8d1004f2727d64100527f2b208d305017)) + ## [1.15.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.3...v1.15.0) (2021-03-10) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index b445ef9c24ca..f6975fa03cec 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.15.0" +version = "1.16.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From f6cf6823e965ea18871cdd6ea448a04ed12ab70e Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 12 Mar 2021 14:52:02 -0800 Subject: [PATCH 124/272] feat: add preserving_proto_field_name to to_json (#213) --- packages/proto-plus/proto/message.py | 9 +++++++-- packages/proto-plus/tests/test_json.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 8ed2a69a9f45..68e5bb0a14bf 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -332,7 +332,8 @@ def to_json( instance, *, use_integers_for_enums=True, - including_default_value_fields=True + including_default_value_fields=True, + preserving_proto_field_name=False, ) -> str: """Given a message instance, serialize it to json @@ -342,6 +343,9 @@ 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. + 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. Returns: str: The json string representation of the protocol buffer. @@ -350,6 +354,7 @@ def to_json( 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, ) def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": @@ -620,7 +625,7 @@ def __init__( package: str, full_name: str, marshal: Marshal, - options: descriptor_pb2.MessageOptions + options: descriptor_pb2.MessageOptions, ) -> None: self.package = package self.full_name = full_name diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index 4b7be8be96c9..d1cbc1fb50f6 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -136,3 +136,15 @@ class Octopus(proto.Message): # Don't permit unknown fields by default with pytest.raises(ParseError): o = Octopus.from_json(json_str) + + +def test_json_snake_case(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + json_str = '{\n "mass_kg": 20\n}' + s = Squid.from_json(json_str) + + assert s.mass_kg == 20 + + assert Squid.to_json(s, preserving_proto_field_name=True) == json_str From 421f2f00081812f287690ab3ca2c82d55a1c7384 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 12 Mar 2021 23:00:06 +0000 Subject: [PATCH 125/272] chore: release 1.17.0 (#214) :robot: I have created a release \*beep\* \*boop\* --- ## [1.17.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.16.0...v1.17.0) (2021-03-12) ### Features * add preserving_proto_field_name to to_json ([#213](https://www.github.com/googleapis/proto-plus-python/issues/213)) ([b2c245b](https://www.github.com/googleapis/proto-plus-python/commit/b2c245bf044b964897f4e7423ff4944ae915e469)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 4db46b5f2ce2..98d229d9d505 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.17.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.16.0...v1.17.0) (2021-03-12) + + +### Features + +* add preserving_proto_field_name to to_json ([#213](https://www.github.com/googleapis/proto-plus-python/issues/213)) ([b2c245b](https://www.github.com/googleapis/proto-plus-python/commit/b2c245bf044b964897f4e7423ff4944ae915e469)) + ## [1.16.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.15.0...v1.16.0) (2021-03-12) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index f6975fa03cec..cb6384a837f0 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.16.0" +version = "1.17.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 064c466df09ebc3af90be40b97ea421d2a356fd2 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 16 Mar 2021 12:14:34 -0700 Subject: [PATCH 126/272] feat: add copy_from method for field assignment (#215) Because of implementation detatils of the underlying protocol buffers runtime, assigning to a proto-plus message field is achieved by copying, not by updating references. This can lead to surprising and unintuitive behavior for developers who are expecting python object behavior, e.g. reference aliases. This PR adds a 'Message.copy_from' method that is semantically identical to regular assignment. This can be used at the discretion of the developer to clarify expected behavior. --- packages/proto-plus/docs/messages.rst | 68 +++++++++++++++++++ .../proto-plus/docs/reference/message.rst | 2 +- packages/proto-plus/proto/message.py | 32 ++++++++- packages/proto-plus/tests/test_message.py | 24 +++++++ 4 files changed, 124 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index af23ea9da59e..e7861b04602c 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -80,6 +80,74 @@ Instantiate messages using either keyword arguments or a :class:`dict` >>> song.title 'Canon in D' + +Assigning to Fields +------------------- + +One of the goals of proto-plus is to make protobufs feel as much like regular python +objects as possible. It is possible to update a message's field by assigning to it, +just as if it were a regular python object. + +.. code-block:: python + + song = Song() + song.composer = Composer(given_name="Johann", family_name="Bach") + + # Can also assign from a dictionary as a convenience. + song.composer = {"given_name": "Claude", "family_name": "Debussy"} + + # Repeated fields can also be assigned + class Album(proto.Message): + songs = proto.RepeatedField(Song, number=1) + + a = Album() + songs = [Song(title="Canon in D"), Song(title="Little Fugue")] + a.songs = songs + +.. note:: + + Assigning to a proto-plus message field works by making copies, not by updating references. + This is necessary because of memory layout requirements of protocol buffers. + These memory constraints are maintained by the protocol buffers runtime. + This behavior can be surprising under certain circumstances, e.g. trying to save + an alias to a nested field. + + :class:`proto.Message` defines a helper message, :meth:`~.Message.copy_from` to + help make the distinction clear when reading code. + The semantics of :meth:`~.Message.copy_from` are identical to the field assignment behavior described above. + + .. code-block:: python + + composer = Composer(given_name="Johann", family_name="Bach") + song = Song(title="Tocatta and Fugue in D Minor", composer=composer) + composer.given_name = "Wilhelm" + + # 'composer' is NOT a reference to song.composer + assert song.composer.given_name == "Johann" + + # We CAN update the song's composer by assignment. + song.composer = composer + composer.given_name = "Carl" + + # 'composer' is STILL not a referene to song.composer. + assert song.composer.given_name == "Wilhelm" + + # It does work in reverse, though, + # if we want a reference we can access then update. + composer = song.composer + composer.given_name = "Gottfried" + + assert song.composer.given_name == "Gottfried" + + # We can use 'copy_from' if we're concerned that the code + # implies that assignment involves references. + composer = Composer(given_name="Elisabeth", family_name="Bach") + # We could also do Message.copy_from(song.composer, composer) instead. + Composer.copy_from(song.composer, composer) + + assert song.composer.given_name == "Elisabeth" + + Enums ----- diff --git a/packages/proto-plus/docs/reference/message.rst b/packages/proto-plus/docs/reference/message.rst index 34da8376fbe9..436b0c819176 100644 --- a/packages/proto-plus/docs/reference/message.rst +++ b/packages/proto-plus/docs/reference/message.rst @@ -11,7 +11,7 @@ Message and Field .. automethod:: to_json .. automethod:: from_json .. automethod:: to_dict - + .. automethod:: copy_from .. automodule:: proto.fields :members: diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 68e5bb0a14bf..e046d6282f31 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -401,6 +401,36 @@ def to_dict( use_integers_for_enums=use_integers_for_enums, ) + def copy_from(cls, instance, other): + """Equivalent for protobuf.Message.CopyFrom + + Args: + instance: An instance of this message type + other: (Union[dict, ~.Message): + A dictionary or message to reinitialize the values for this message. + """ + if isinstance(other, cls): + # Just want the underlying proto. + other = Message.pb(other) + elif isinstance(other, cls.pb()): + # Don't need to do anything. + pass + elif isinstance(other, collections.abc.Mapping): + # Coerce into a proto + other = cls._meta.pb(**other) + else: + raise TypeError( + "invalid argument type to copy to {}: {}".format( + cls.__name__, other.__class__.__name__ + ) + ) + + # Note: we can't just run self.__init__ because this may be a message field + # for a higher order proto; the memory layout for protos is NOT LIKE the + # python memory model. We cannot rely on just setting things by reference. + # Non-trivial complexity is (partially) hidden by the protobuf runtime. + cls.pb(instance).CopyFrom(other) + class Message(metaclass=MessageMeta): """The abstract base class for a message. @@ -436,7 +466,7 @@ def __init__(self, mapping=None, *, ignore_unknown_fields=False, **kwargs): # # The `wrap` method on the metaclass is the public API for taking # ownership of the passed in protobuf objet. - mapping = copy.copy(mapping) + mapping = copy.deepcopy(mapping) if kwargs: mapping.MergeFrom(self._meta.pb(**kwargs)) diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index b723fcdeaf60..a77f554a6e72 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -317,3 +317,27 @@ class Squid(proto.Message): s = Squid({"mass_kg": 20, "length_cm": 100}, ignore_unknown_fields=True) assert not hasattr(s, "length_cm") + + +def test_copy_from(): + class Mollusc(proto.Message): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + + squid = proto.Field(Squid, number=1) + + m = Mollusc() + s = Mollusc.Squid(mass_kg=20) + Mollusc.Squid.copy_from(m.squid, s) + assert m.squid is not s + assert m.squid == s + + s.mass_kg = 30 + Mollusc.Squid.copy_from(m.squid, Mollusc.Squid.pb(s)) + assert m.squid == s + + Mollusc.Squid.copy_from(m.squid, {"mass_kg": 10}) + assert m.squid.mass_kg == 10 + + with pytest.raises(TypeError): + Mollusc.Squid.copy_from(m.squid, (("mass_kg", 20))) From 3457534b56c6b4390388074a0f669bca1ac68c1f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 16 Mar 2021 19:18:02 +0000 Subject: [PATCH 127/272] chore: release 1.18.0 (#216) :robot: I have created a release \*beep\* \*boop\* --- ## [1.18.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.17.0...v1.18.0) (2021-03-16) ### Features * add copy_from method for field assignment ([#215](https://www.github.com/googleapis/proto-plus-python/issues/215)) ([11c3e58](https://www.github.com/googleapis/proto-plus-python/commit/11c3e58a9ba59f0d7d808a26597dab735ca982ba)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 98d229d9d505..23d1c6442a9a 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.18.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.17.0...v1.18.0) (2021-03-16) + + +### Features + +* add copy_from method for field assignment ([#215](https://www.github.com/googleapis/proto-plus-python/issues/215)) ([11c3e58](https://www.github.com/googleapis/proto-plus-python/commit/11c3e58a9ba59f0d7d808a26597dab735ca982ba)) + ## [1.17.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.16.0...v1.17.0) (2021-03-12) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index cb6384a837f0..b4914d2e943c 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.17.0" +version = "1.18.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From c99529b54c4cb26244597949c760967c5e1dfc94 Mon Sep 17 00:00:00 2001 From: odidev Date: Fri, 19 Mar 2021 23:44:28 +0530 Subject: [PATCH 128/272] fix: Add arm64 support for PY3.6 (#219) Build with python3.6 using protobuf v3.12.0. Its wheel and source tar are not available for arm64 to build from source. So upgraded protobuf to recent version having source tar. Signed-off-by: odidev --- packages/proto-plus/testing/constraints-3.6.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/testing/constraints-3.6.txt b/packages/proto-plus/testing/constraints-3.6.txt index f7365f65e449..31111ae4ffaf 100644 --- a/packages/proto-plus/testing/constraints-3.6.txt +++ b/packages/proto-plus/testing/constraints-3.6.txt @@ -5,5 +5,5 @@ # # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 -protobuf==3.12.0 -google-api-core==1.22.2 \ No newline at end of file +protobuf==3.15.6 +google-api-core==1.22.2 From 53d4d81100fd4c65614a9db3b1f613fb4c87bc62 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:13:02 -0700 Subject: [PATCH 129/272] chore: release 1.18.1 (#220) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 23d1c6442a9a..81f2c4a9f261 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.18.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.18.0...v1.18.1) (2021-03-19) + + +### Bug Fixes + +* Add arm64 support for PY3.6 ([#219](https://www.github.com/googleapis/proto-plus-python/issues/219)) ([c9667c2](https://www.github.com/googleapis/proto-plus-python/commit/c9667c22d0b8f6026dbf69d502eb8eb972279891)) + ## [1.18.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.17.0...v1.18.0) (2021-03-16) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index b4914d2e943c..df4fa9647f17 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.18.0" +version = "1.18.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 36ec8ddfec35d66c8e1ba45e7d39d5f9ec19c6f0 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 12 Apr 2021 22:59:27 +0200 Subject: [PATCH 130/272] chore(deps): update styfle/cancel-workflow-action action to v0.9.0 (#225) --- packages/proto-plus/.github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index b64a74626f3f..7da71521db0f 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 + uses: styfle/cancel-workflow-action@0.9.0 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 + uses: styfle/cancel-workflow-action@0.9.0 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 @@ -54,7 +54,7 @@ jobs: variant: cpp steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 + uses: styfle/cancel-workflow-action@0.9.0 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 From 9990a8edb1a593be1f66fc22dca0811363f76570 Mon Sep 17 00:00:00 2001 From: Dan Lee <71398022+dandhlee@users.noreply.github.com> Date: Fri, 16 Apr 2021 17:27:57 -0400 Subject: [PATCH 131/272] chore: prevent normalization of semver versioning (#227) * chore: prevent normalization of semver versioning * chore: update style for consistency * chore: update workaround to make sic work --- packages/proto-plus/setup.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index df4fa9647f17..15062ffb8843 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -15,7 +15,22 @@ import io import os -from setuptools import find_packages, setup +import setuptools + +# Disable version normalization performed by setuptools.setup() +try: + # Try the approach of using sic(), added in setuptools 46.1.0 + from setuptools import sic +except ImportError: + # Try the approach of replacing packaging.version.Version + sic = lambda v: v + try: + # setuptools >=39.0.0 uses packaging from setuptools.extern + from setuptools.extern import packaging + except ImportError: + # setuptools <39.0.0 uses packaging from pkg_resources.extern + from pkg_resources.extern import packaging + packaging.version.Version = packaging.version.LegacyVersion version = "1.18.1" @@ -24,14 +39,14 @@ with io.open(os.path.join(PACKAGE_ROOT, "README.rst")) as file_obj: README = file_obj.read() -setup( +setuptools.setup( name="proto-plus", - version=version, + version=sic(version), license="Apache 2.0", author="Google LLC", author_email="googleapis-packages@google.com", url="https://github.com/googleapis/proto-plus-python.git", - packages=find_packages(exclude=["docs", "tests"]), + packages=setuptools.find_packages(exclude=["docs", "tests"]), description="Beautiful, Pythonic protocol buffers.", long_description=README, platforms="Posix; MacOS X", From 460a4d235920376b1f5ab9db0300ad976cb57c29 Mon Sep 17 00:00:00 2001 From: Dan Lee <71398022+dandhlee@users.noreply.github.com> Date: Fri, 23 Apr 2021 19:13:57 -0400 Subject: [PATCH 132/272] chore(revert): revert preventing normalization (#228) --- packages/proto-plus/setup.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 15062ffb8843..df4fa9647f17 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -15,22 +15,7 @@ import io import os -import setuptools - -# Disable version normalization performed by setuptools.setup() -try: - # Try the approach of using sic(), added in setuptools 46.1.0 - from setuptools import sic -except ImportError: - # Try the approach of replacing packaging.version.Version - sic = lambda v: v - try: - # setuptools >=39.0.0 uses packaging from setuptools.extern - from setuptools.extern import packaging - except ImportError: - # setuptools <39.0.0 uses packaging from pkg_resources.extern - from pkg_resources.extern import packaging - packaging.version.Version = packaging.version.LegacyVersion +from setuptools import find_packages, setup version = "1.18.1" @@ -39,14 +24,14 @@ with io.open(os.path.join(PACKAGE_ROOT, "README.rst")) as file_obj: README = file_obj.read() -setuptools.setup( +setup( name="proto-plus", - version=sic(version), + version=version, license="Apache 2.0", author="Google LLC", author_email="googleapis-packages@google.com", url="https://github.com/googleapis/proto-plus-python.git", - packages=setuptools.find_packages(exclude=["docs", "tests"]), + packages=find_packages(exclude=["docs", "tests"]), description="Beautiful, Pythonic protocol buffers.", long_description=README, platforms="Posix; MacOS X", From 79664e4d2ed55efdc57afb926da77c7a5a12010f Mon Sep 17 00:00:00 2001 From: "google-cloud-policy-bot[bot]" <80869356+google-cloud-policy-bot[bot]@users.noreply.github.com> Date: Wed, 28 Apr 2021 09:58:58 -0700 Subject: [PATCH 133/272] chore: add SECURITY.md (#229) Co-authored-by: google-cloud-policy-bot[bot] <80869356+google-cloud-policy-bot[bot]@users.noreply.github.com> --- packages/proto-plus/SECURITY.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/proto-plus/SECURITY.md diff --git a/packages/proto-plus/SECURITY.md b/packages/proto-plus/SECURITY.md new file mode 100644 index 000000000000..8b58ae9c01ae --- /dev/null +++ b/packages/proto-plus/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). + +The Google Security Team will respond within 5 working days of your report on g.co/vulnz. + +We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. From b135f80e70c99f698fd5a5224a08edec2044611b Mon Sep 17 00:00:00 2001 From: vishwaja13 <26506063+vishwaja13@users.noreply.github.com> Date: Tue, 29 Jun 2021 22:08:37 +0530 Subject: [PATCH 134/272] feat: pass 'including_default_value_fields' through to 'Message.to_dict' method (#232) Solution to the following issue #231, should work as boolean value and according to the value the GoogleAdsRow default columns should or should not return in results. If including_default_value_fields is False default columns should not return and if True default values should return now. Closes #231. --- packages/proto-plus/proto/message.py | 12 ++++++++++-- packages/proto-plus/tests/test_message.py | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index e046d6282f31..00ec4cc7c860 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -375,7 +375,12 @@ def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": return instance def to_dict( - cls, instance, *, use_integers_for_enums=True, preserving_proto_field_name=True + cls, + instance, + *, + use_integers_for_enums=True, + preserving_proto_field_name=True, + including_default_value_fields=True, ) -> "Message": """Given a message instance, return its representation as a python dict. @@ -388,6 +393,9 @@ def to_dict( 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 + determines whether the default field values should be included in the results. + Default is True. Returns: dict: A representation of the protocol buffer using pythonic data structures. @@ -396,7 +404,7 @@ def to_dict( """ return MessageToDict( cls.pb(instance), - including_default_value_fields=True, + including_default_value_fields=including_default_value_fields, preserving_proto_field_name=preserving_proto_field_name, use_integers_for_enums=use_integers_for_enums, ) diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index a77f554a6e72..5351dbd77165 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -262,6 +262,11 @@ class Color(proto.Enum): s_dict = Squid.to_dict(s, use_integers_for_enums=False) assert s_dict["chromatophores"][0]["color"] == "RED" + s_new_2 = Squid(mass_kg=20) + s_dict_2 = Squid.to_dict(s_new_2, including_default_value_fields=False) + expected_dict = {"mass_kg": 20} + assert s_dict_2 == expected_dict + new_s = Squid(s_dict) assert new_s == s From f431b1e38600ed14718edac9d4cb423f9eb25f27 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 29 Jun 2021 09:42:01 -0700 Subject: [PATCH 135/272] chore: release 1.19.0 (#233) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 81f2c4a9f261..eaf77f1a0668 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.19.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.18.1...v1.19.0) (2021-06-29) + + +### Features + +* pass 'including_default_value_fields' through to 'Message.to_dict' method ([#232](https://www.github.com/googleapis/proto-plus-python/issues/232)) ([15c2f47](https://www.github.com/googleapis/proto-plus-python/commit/15c2f479f81f0f80d451ca9b043e42cecfe7184e)) + ### [1.18.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.18.0...v1.18.1) (2021-03-19) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index df4fa9647f17..b6bc503a1712 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.18.1" +version = "1.19.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 02c97c1264177ec6e9be3d64ebc7271a8db8e3b7 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 29 Jun 2021 09:46:23 -0700 Subject: [PATCH 136/272] chore: enable cpp tests for 3.9 (#234) Binary wheels for protoc for python 3.9 have been published, so the corresponding unit tests should be enabled. --- packages/proto-plus/.github/workflows/tests.yml | 4 ---- packages/proto-plus/noxfile.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 7da71521db0f..93d7308af09a 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -48,10 +48,6 @@ jobs: matrix: python: [3.6, 3.7, 3.8, 3.9] variant: ['', cpp] - # Note: as of 2021-02-09, there are no 3.9 python wheels for protobuf/grpc - exclude: - - python: 3.9 - variant: cpp steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@0.9.0 diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 0ed6caa7178a..1ca07cecb607 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -54,7 +54,7 @@ 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"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9"]) def unitcpp(session): return unit(session, proto="cpp") From 347397c3f132f859c914695803584505a78a4002 Mon Sep 17 00:00:00 2001 From: "google-cloud-policy-bot[bot]" <80869356+google-cloud-policy-bot[bot]@users.noreply.github.com> Date: Tue, 13 Jul 2021 09:20:23 +0000 Subject: [PATCH 137/272] chore: add CONTRIBUTING.md (#239) chore: add CONTRIBUTING.md --- packages/proto-plus/CONTRIBUTING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/proto-plus/CONTRIBUTING.md diff --git a/packages/proto-plus/CONTRIBUTING.md b/packages/proto-plus/CONTRIBUTING.md new file mode 100644 index 000000000000..6272489dae31 --- /dev/null +++ b/packages/proto-plus/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code Reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google/conduct/). From 3516cb714b17e2a89e139e22da18f795d1424f4a Mon Sep 17 00:00:00 2001 From: "google-cloud-policy-bot[bot]" <80869356+google-cloud-policy-bot[bot]@users.noreply.github.com> Date: Tue, 13 Jul 2021 09:24:23 +0000 Subject: [PATCH 138/272] chore: add a Code of Conduct (#223) add a code of conduct --- packages/proto-plus/CODE_OF_CONDUCT.md | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/proto-plus/CODE_OF_CONDUCT.md diff --git a/packages/proto-plus/CODE_OF_CONDUCT.md b/packages/proto-plus/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..46b2a08ea6d1 --- /dev/null +++ b/packages/proto-plus/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, +and in the interest of fostering an open and welcoming community, +we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project +a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, +such as physical or electronic +addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct +may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by opening an issue +or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, +available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) From 8f8deea1046c6b1ee506f5e473723aa82fcb208e Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 29 Sep 2021 12:18:42 -0400 Subject: [PATCH 139/272] fix: ensure enums are incomparable w other enum types (#248) Fixes #247. --- packages/proto-plus/proto/enums.py | 40 ++++++++- packages/proto-plus/tests/enums_test.py | 28 ++++++ .../tests/test_enum_total_ordering.py | 87 +++++++++++++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 packages/proto-plus/tests/enums_test.py create mode 100644 packages/proto-plus/tests/test_enum_total_ordering.py diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 1fe8746f1ca9..067d9674c0dc 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -108,7 +108,45 @@ def __new__(mcls, name, bases, attrs): class Enum(enum.IntEnum, metaclass=ProtoEnumMeta): """A enum object that also builds a protobuf enum descriptor.""" - pass + def _comparable(self, other): + # Avoid 'isinstance' to prevent other IntEnums from matching + return type(other) in (type(self), int) + + def __eq__(self, other): + if not self._comparable(other): + return NotImplemented + + return self.value == int(other) + + def __ne__(self, other): + if not self._comparable(other): + return NotImplemented + + return self.value != int(other) + + def __lt__(self, other): + if not self._comparable(other): + return NotImplemented + + return self.value < int(other) + + def __le__(self, other): + if not self._comparable(other): + return NotImplemented + + return self.value <= int(other) + + def __ge__(self, other): + if not self._comparable(other): + return NotImplemented + + return self.value >= int(other) + + def __gt__(self, other): + if not self._comparable(other): + return NotImplemented + + return self.value > int(other) class _EnumInfo: diff --git a/packages/proto-plus/tests/enums_test.py b/packages/proto-plus/tests/enums_test.py new file mode 100644 index 000000000000..6639fbf9cf7c --- /dev/null +++ b/packages/proto-plus/tests/enums_test.py @@ -0,0 +1,28 @@ +# Copyright (C) 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + +__protobuf__ = proto.module(package="test.proto", manifest={"Enums",},) + + +class OneEnum(proto.Enum): + UNSPECIFIED = 0 + SOME_VALUE = 1 + + +class OtherEnum(proto.Enum): + UNSPECIFIED = 0 + APPLE = 1 + BANANA = 2 diff --git a/packages/proto-plus/tests/test_enum_total_ordering.py b/packages/proto-plus/tests/test_enum_total_ordering.py new file mode 100644 index 000000000000..a459e6b0d697 --- /dev/null +++ b/packages/proto-plus/tests/test_enum_total_ordering.py @@ -0,0 +1,87 @@ +# Copyright 2021, Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import enums_test + + +def test_total_ordering_w_same_enum_type(): + to_compare = enums_test.OneEnum.SOME_VALUE + + for item in enums_test.OneEnum: + if item.value < to_compare.value: + assert not to_compare == item + assert to_compare != item + assert not to_compare < item + assert not to_compare <= item + assert to_compare > item + assert to_compare >= item + elif item.value > to_compare.value: + assert not to_compare == item + assert to_compare != item + assert to_compare < item + assert to_compare <= item + assert not to_compare > item + assert not to_compare >= item + else: # item.value == to_compare.value: + assert to_compare == item + assert not to_compare != item + assert not to_compare < item + assert to_compare <= item + assert not to_compare > item + assert to_compare >= item + + +def test_total_ordering_w_other_enum_type(): + to_compare = enums_test.OneEnum.SOME_VALUE + + for item in enums_test.OtherEnum: + assert not to_compare == item + assert to_compare.SOME_VALUE != item + with pytest.raises(TypeError): + assert not to_compare < item + with pytest.raises(TypeError): + assert not to_compare <= item + with pytest.raises(TypeError): + assert not to_compare > item + with pytest.raises(TypeError): + assert not to_compare >= item + + +@pytest.mark.parametrize("int_val", range(-1, 3)) +def test_total_ordering_w_int(int_val): + to_compare = enums_test.OneEnum.SOME_VALUE + + if int_val < to_compare.value: + assert not to_compare == int_val + assert to_compare != int_val + assert not to_compare < int_val + assert not to_compare <= int_val + assert to_compare > int_val + assert to_compare >= int_val + elif int_val > to_compare.value: + assert not to_compare == int_val + assert to_compare != int_val + assert to_compare < int_val + assert to_compare <= int_val + assert not to_compare > int_val + assert not to_compare >= int_val + else: # int_val == to_compare.value: + assert to_compare == int_val + assert not to_compare != int_val + assert not to_compare < int_val + assert to_compare <= int_val + assert not to_compare > int_val + assert to_compare >= int_val From 89ae6f06fec6bf5eace55db65f456be02238d058 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 29 Sep 2021 12:20:14 -0400 Subject: [PATCH 140/272] chore: release 1.19.1 (#250) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index eaf77f1a0668..d52437043166 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.0...v1.19.1) (2021-09-29) + + +### Bug Fixes + +* ensure enums are incomparable w other enum types ([#248](https://www.github.com/googleapis/proto-plus-python/issues/248)) ([5927c14](https://www.github.com/googleapis/proto-plus-python/commit/5927c1400f400b3213c9b92e7a37c3c3a1abd681)) + ## [1.19.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.18.1...v1.19.0) (2021-06-29) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index b6bc503a1712..a49c37622ca2 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.0" +version = "1.19.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 4f4956e54ce3e973c08f0664444f002240037af1 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 29 Sep 2021 13:16:35 -0400 Subject: [PATCH 141/272] fix: ensure enums are hashable (#252) Closes #251. --- packages/proto-plus/proto/enums.py | 3 +++ packages/proto-plus/tests/test_enum_total_ordering.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 067d9674c0dc..6f13d32ea201 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -112,6 +112,9 @@ def _comparable(self, other): # Avoid 'isinstance' to prevent other IntEnums from matching return type(other) in (type(self), int) + def __hash__(self): + return hash(self.value) + def __eq__(self, other): if not self._comparable(other): return NotImplemented diff --git a/packages/proto-plus/tests/test_enum_total_ordering.py b/packages/proto-plus/tests/test_enum_total_ordering.py index a459e6b0d697..ad7a36910bcf 100644 --- a/packages/proto-plus/tests/test_enum_total_ordering.py +++ b/packages/proto-plus/tests/test_enum_total_ordering.py @@ -85,3 +85,9 @@ def test_total_ordering_w_int(int_val): assert to_compare <= int_val assert not to_compare > int_val assert to_compare >= int_val + + +def test_hashing(): + to_hash = enums_test.OneEnum.SOME_VALUE + + {to_hash: "testing"} # no raise From 0a17fd9049241fc9c53912f49d38096fafbf58f9 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 29 Sep 2021 17:18:40 +0000 Subject: [PATCH 142/272] chore: release 1.19.2 (#253) :robot: I have created a release \*beep\* \*boop\* --- ### [1.19.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.1...v1.19.2) (2021-09-29) ### Bug Fixes * ensure enums are hashable ([#252](https://www.github.com/googleapis/proto-plus-python/issues/252)) ([232341b](https://www.github.com/googleapis/proto-plus-python/commit/232341b4f4902fba1b3597bb1e1618b8f320374b)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index d52437043166..09232b6d1766 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.1...v1.19.2) (2021-09-29) + + +### Bug Fixes + +* ensure enums are hashable ([#252](https://www.github.com/googleapis/proto-plus-python/issues/252)) ([232341b](https://www.github.com/googleapis/proto-plus-python/commit/232341b4f4902fba1b3597bb1e1618b8f320374b)) + ### [1.19.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.0...v1.19.1) (2021-09-29) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index a49c37622ca2..60e829a84a48 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.1" +version = "1.19.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From f2d14aa2f450f603385db18598c9269cf1afd42a Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 7 Oct 2021 14:05:55 -0700 Subject: [PATCH 143/272] fix: setting bytes field from python string base64 decodes before assignment (#255) This is necessary for maintaining field integrity during round trip conversions to python dicts. E.g. class MyMessage(proto.Message): data = proto.Field(proto.BYTES, number=1) my_message = MyMessage(data=b"this is a data payload") assert my_message == MyMessage(MyMessage.to_dict(my_message)) Conversion to and from json does not exhibit this problem because the entire ser/des logic is encapuslated by the vanilla protobuf runtime, which handles this problem. Fix for #249 --- packages/proto-plus/docs/marshal.rst | 28 ++++++++++++ packages/proto-plus/proto/fields.py | 7 +-- packages/proto-plus/proto/marshal/marshal.py | 13 +++--- .../proto-plus/proto/marshal/rules/bytes.py | 44 +++++++++++++++++++ .../proto-plus/tests/test_fields_bytes.py | 25 ++++++++++- .../proto-plus/tests/test_marshal_register.py | 13 ------ 6 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 packages/proto-plus/proto/marshal/rules/bytes.py diff --git a/packages/proto-plus/docs/marshal.rst b/packages/proto-plus/docs/marshal.rst index 369d823d6472..1d2dccccd9b4 100644 --- a/packages/proto-plus/docs/marshal.rst +++ b/packages/proto-plus/docs/marshal.rst @@ -44,6 +44,34 @@ Protocol buffer type Python type Nullable If you *write* a timestamp field using a Python ``datetime`` value, any existing nanosecond precision will be overwritten. +.. note:: + + Setting a ``bytes`` field from a string value will first base64 decode the string. + This is necessary to preserve the original protobuf semantics when converting between + Python dicts and proto messages. + Converting a message containing a bytes field to a dict will + base64 encode the bytes field and yield a value of type str. + +.. code-block:: python + + import proto + from google.protobuf.json_format import ParseDict + + class MyMessage(proto.Message): + data = proto.Field(proto.BYTES, number=1) + + msg = MyMessage(data=b"this is a message") + msg_dict = MyMessage.to_dict(msg) + + # Note: the value is the base64 encoded string of the bytes field. + # It has a type of str, NOT bytes. + assert type(msg_dict['data']) == str + + msg_pb = ParseDict(msg_dict, MyMessage.pb()) + msg_two = MyMessage(msg_dict) + + assert msg == msg_pb == msg_two + Wrapper types ------------- diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index cc98e8b01607..b58349254c05 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -126,14 +126,15 @@ def package(self) -> str: @property def pb_type(self): - """Return the composite type of the field, or None for primitives.""" + """Return the composite type of the field, or the primitive type if a primitive.""" # For enums, return the Python enum. if self.enum: return self.enum - # For non-enum primitives, return None. + # For primitive fields, we still want to know + # what the type is. if not self.message: - return None + return self.proto_type # Return the internal protobuf message. if hasattr(self.message, "_meta"): diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index c8224ce25038..d0dc2ead7643 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -25,9 +25,11 @@ from proto.marshal.collections import MapComposite from proto.marshal.collections import Repeated from proto.marshal.collections import RepeatedComposite +from proto.marshal.rules import bytes as pb_bytes from proto.marshal.rules import dates from proto.marshal.rules import struct from proto.marshal.rules import wrappers +from proto.primitives import ProtoType class Rule(abc.ABC): @@ -85,14 +87,6 @@ class TimestampRule: proto_type (type): A protocol buffer message type. rule: A marshal object """ - # Sanity check: Do not register anything to a class that is not - # a protocol buffer message. - if not issubclass(proto_type, (message.Message, enum.IntEnum)): - raise TypeError( - "Only enums and protocol buffer messages may be " - "registered to the marshal." - ) - # If a rule was provided, register it and be done. if rule: # Ensure the rule implements Rule. @@ -150,6 +144,9 @@ def reset(self): self.register(struct_pb2.ListValue, struct.ListValueRule(marshal=self)) self.register(struct_pb2.Struct, struct.StructRule(marshal=self)) + # Special case for bytes to allow base64 encode/decode + self.register(ProtoType.BYTES, pb_bytes.BytesRule()) + def to_python(self, proto_type, value, *, absent: bool = None): # Internal protobuf has its own special type for lists of values. # Return a view around it that implements MutableSequence. diff --git a/packages/proto-plus/proto/marshal/rules/bytes.py b/packages/proto-plus/proto/marshal/rules/bytes.py new file mode 100644 index 000000000000..9c0890aa7066 --- /dev/null +++ b/packages/proto-plus/proto/marshal/rules/bytes.py @@ -0,0 +1,44 @@ +# Copyright (C) 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import base64 + + +class BytesRule: + """A marshal between Python strings and protobuf bytes. + + Note: this conversion is asymmetric becasue Python does have a bytes type. + It is sometimes necessary to convert proto bytes fields to strings, e.g. for + JSON encoding, marshalling a message to a dict. Because bytes fields can + represent arbitrary data, bytes fields are base64 encoded when they need to + be represented as strings. + + It is necessary to have the conversion be bidirectional, i.e. + my_message == MyMessage(MyMessage.to_dict(my_message)) + + To accomplish this, we need to intercept assignments from strings and + base64 decode them back into bytes. + """ + + def to_python(self, value, *, absent: bool = None): + return value + + def to_proto(self, value): + if isinstance(value, str): + value = value.encode("utf-8") + value += b"=" * (4 - len(value) % 4) # padding + value = base64.urlsafe_b64decode(value) + + return value diff --git a/packages/proto-plus/tests/test_fields_bytes.py b/packages/proto-plus/tests/test_fields_bytes.py index 3e595d7a3897..a141c855bae4 100644 --- a/packages/proto-plus/tests/test_fields_bytes.py +++ b/packages/proto-plus/tests/test_fields_bytes.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import pytest import proto @@ -71,5 +72,25 @@ class Foo(proto.Message): # for strings (but not vice versa). foo.bar = b"anything" assert foo.bar == "anything" - with pytest.raises(TypeError): - foo.baz = "anything" + + # We need to permit setting bytes fields from strings, + # but the marshalling needs to base64 decode the result. + # This is a requirement for interop with the vanilla protobuf runtime: + # converting a proto message to a dict base64 encodes the bytes + # becase it may be sent over the network via a protocol like HTTP. + encoded_swallow: str = base64.urlsafe_b64encode(b"unladen swallow").decode("utf-8") + assert type(encoded_swallow) == str + foo.baz = encoded_swallow + assert foo.baz == b"unladen swallow" + + +def test_bytes_to_dict_bidi(): + class Foo(proto.Message): + bar = proto.Field(proto.BYTES, number=1) + + foo = Foo(bar=b"spam") + + foo_dict = Foo.to_dict(foo) + foo_two = Foo(foo_dict) + + assert foo == foo_two diff --git a/packages/proto-plus/tests/test_marshal_register.py b/packages/proto-plus/tests/test_marshal_register.py index 8a9597f38a73..3ca1a2a88d73 100644 --- a/packages/proto-plus/tests/test_marshal_register.py +++ b/packages/proto-plus/tests/test_marshal_register.py @@ -33,19 +33,6 @@ def to_python(self, value, *, absent=None): assert isinstance(marshal._rules[empty_pb2.Empty], Rule) -def test_invalid_target_registration(): - marshal = BaseMarshal() - with pytest.raises(TypeError): - - @marshal.register(object) - class Rule: - def to_proto(self, value): - return value - - def to_python(self, value, *, absent=None): - return value - - def test_invalid_marshal_class(): marshal = BaseMarshal() with pytest.raises(TypeError): From d74c79182fe73627440db2967929edbfd1489004 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 7 Oct 2021 14:54:19 -0700 Subject: [PATCH 144/272] chore: release 1.19.3 (#259) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 09232b6d1766..d28e9519b5ae 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.2...v1.19.3) (2021-10-07) + + +### Bug Fixes + +* setting bytes field from python string base64 decodes before assignment ([#255](https://www.github.com/googleapis/proto-plus-python/issues/255)) ([b6f3eb6](https://www.github.com/googleapis/proto-plus-python/commit/b6f3eb6575484748126170997b8c98512763ea66)) + ### [1.19.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.1...v1.19.2) (2021-09-29) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 60e829a84a48..f6b9c23bb6aa 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.2" +version = "1.19.3" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From d0c2c6ecdcc5e69e980ac72d4c4a56c3945c32b7 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Fri, 8 Oct 2021 09:44:58 -0700 Subject: [PATCH 145/272] docs: clarify that proto plus messages are not pickleable (#260) Clarify that proto-plus messages are not pickleable, describe the alternative, and give a multiprocessing example. Note: in the future, we _may_ enable pickling proto-plus messages through custom extensions as described in https://docs.python.org/3/library/pickle.html#pickling-class-instances This is _not_ guaranteed. For the foreseeable future, the preferred method of serializing and deserializing proto-plus messages is Message.serialize and Message.deserialize --- packages/proto-plus/docs/messages.rst | 29 +++++++++++++++++++++++++++ packages/proto-plus/noxfile.py | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index e7861b04602c..e83a2246a905 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -243,3 +243,32 @@ already allows construction from mapping types. song_dict = Song.to_dict(song) new_song = Song(song_dict) + +.. note:: + + Protobuf messages **CANNOT** be safely pickled or unpickled. This has serious consequences for programs that use multiprocessing or write messages to files. + The preferred mechanism for serializing proto messages is :meth:`~.Message.serialize`. + + Multiprocessing example: + + .. code-block:: python + + import proto + from multiprocessing import Pool + + class Composer(proto.Message): + name = proto.Field(proto.STRING, number=1) + genre = proto.Field(proto.STRING, number=2) + + composers = [Composer(name=n) for n in ["Bach", "Mozart", "Brahms", "Strauss"]] + + with multiprocessing.Pool(2) as p: + def add_genre(comp_bytes): + composer = Composer.deserialize(comp_bytes) + composer.genre = "classical" + return Composer.serialize(composer) + + updated_composers = [ + Composer.deserialize(comp_bytes) + for comp_bytes in p.map(add_genre, (Composer.serialize(comp) for comp in composers)) + ] diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 1ca07cecb607..35d937d3b080 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -59,7 +59,7 @@ def unitcpp(session): return unit(session, proto="cpp") -@nox.session(python="3.7") +@nox.session(python="3.9") def docs(session): """Build the docs.""" From e66207ea972ef6022c44520b1152389303c0f892 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 10:33:01 -0700 Subject: [PATCH 146/272] chore: release 1.19.4 (#262) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index d28e9519b5ae..fde1d580ed85 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.4](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.3...v1.19.4) (2021-10-08) + + +### Documentation + +* clarify that proto plus messages are not pickleable ([#260](https://www.github.com/googleapis/proto-plus-python/issues/260)) ([6e691dc](https://www.github.com/googleapis/proto-plus-python/commit/6e691dc27b1e540ef0661597fd89ece8f0155c97)) + ### [1.19.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.2...v1.19.3) (2021-10-07) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index f6b9c23bb6aa..38cb875c229c 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.3" +version = "1.19.4" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 5f6b24a2d96a52360dedf4dee3b8d38fbac559b5 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 11 Oct 2021 13:41:23 -0700 Subject: [PATCH 147/272] docs: Clarify semantics of multiple oneof variants passed to msg ctor (#263) Example: import proto class Song(proto.Message): name = proto.Field(proto.STRING, number=1, oneof="identifier") database_id = proto.Field(proto.STRING, number=2, oneof="identifier") s = Song(name="Canon in D minor", database_id="b5a37aad3") assert "database_id" in s and "name" not in s s = Song(database_id="e6aa708c7e", name="Little Fugue") --- packages/proto-plus/docs/fields.rst | 30 +++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/docs/fields.rst b/packages/proto-plus/docs/fields.rst index 07e70fbf5bc6..b930c61a43fa 100644 --- a/packages/proto-plus/docs/fields.rst +++ b/packages/proto-plus/docs/fields.rst @@ -151,6 +151,33 @@ a string (which should match for all fields within the oneof): have consecutive field numbers, but they must be declared in consecutive order. +.. warning:: + + If a message is constructed with multiple variants of a single ``oneof`` passed + to its constructor, the **last** keyword/value pair passed will be the final + value set. + + This is consistent with PEP-468_, which specifies the order that keyword args + are seen by called functions, and with the regular protocol buffers runtime, + which exhibits the same behavior. + + Example: + + .. code-block:: python + + import proto + + class Song(proto.Message): + name = proto.Field(proto.STRING, number=1, oneof="identifier") + database_id = proto.Field(proto.STRING, number=2, oneof="identifier") + + s = Song(name="Canon in D minor", database_id="b5a37aad3") + assert "database_id" in s and "name" not in s + + s = Song(database_id="e6aa708c7e", name="Little Fugue") + assert "name" in s and "database_id" not in s + + Optional fields --------------- @@ -199,5 +226,4 @@ information. .. _documentation: https://github.com/protocolbuffers/protobuf/blob/v3.12.0/docs/field_presence.md - - +.. _PEP-468: https://www.python.org/dev/peps/pep-0468/ From 7cd86a5bef6fb44d74c26102006b917678f2edd0 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 14:41:55 -0700 Subject: [PATCH 148/272] chore: release 1.19.5 (#264) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index fde1d580ed85..4e6505f08fea 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.5](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.4...v1.19.5) (2021-10-11) + + +### Documentation + +* Clarify semantics of multiple oneof variants passed to msg ctor ([#263](https://www.github.com/googleapis/proto-plus-python/issues/263)) ([6f8a5b2](https://www.github.com/googleapis/proto-plus-python/commit/6f8a5b2098e4f6748945c53bda3d5821e62e5a0a)) + ### [1.19.4](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.3...v1.19.4) (2021-10-08) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 38cb875c229c..0aba6a545cee 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.4" +version = "1.19.5" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 6224d1d195fb39ae142de481ba57e6101a6f2993 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 25 Oct 2021 11:54:27 -0700 Subject: [PATCH 149/272] fix: setting 64bit fields from strings supported (#267) Due to limitations in certain browser/javascript combinations, fields that are 64 bit integer types are converted to strings when encoding a message to JSON or a dict. Decoding from JSON handles this explicitly, but it makes converting back from a dict an error. This fix adds support for setting these 64 bit fields explicitly from strings. E.g. class Squid(proto.Message): mass_kg = proto.Field(proto.INT64, number=1) s = Squid(mass_kg=10) s_dict = Squid.to_dict(s) # Demonstrate the issue assert type(s_dict["mass_kg"]) == str # Demonstrate the round trip conversion is transparent assert s == Squid(s_dict) s.mass_kg = "20" # Works as an implementation side effect --- packages/proto-plus/docs/marshal.rst | 4 +- packages/proto-plus/proto/marshal/marshal.py | 6 ++ .../proto-plus/proto/marshal/rules/message.py | 11 ++- .../proto/marshal/rules/stringy_numbers.py | 68 +++++++++++++++++++ packages/proto-plus/proto/message.py | 6 +- packages/proto-plus/tests/test_fields_int.py | 43 ++++++++++++ 6 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 packages/proto-plus/proto/marshal/rules/stringy_numbers.py diff --git a/packages/proto-plus/docs/marshal.rst b/packages/proto-plus/docs/marshal.rst index 1d2dccccd9b4..ee61cb2e6846 100644 --- a/packages/proto-plus/docs/marshal.rst +++ b/packages/proto-plus/docs/marshal.rst @@ -71,8 +71,8 @@ Protocol buffer type Python type Nullable msg_two = MyMessage(msg_dict) assert msg == msg_pb == msg_two - - + + Wrapper types ------------- diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index d0dc2ead7643..baac7adcccc3 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -26,6 +26,7 @@ from proto.marshal.collections import Repeated from proto.marshal.collections import RepeatedComposite from proto.marshal.rules import bytes as pb_bytes +from proto.marshal.rules import stringy_numbers from proto.marshal.rules import dates from proto.marshal.rules import struct from proto.marshal.rules import wrappers @@ -147,6 +148,11 @@ def reset(self): # Special case for bytes to allow base64 encode/decode self.register(ProtoType.BYTES, pb_bytes.BytesRule()) + # Special case for int64 from strings because of dict round trip. + # See https://github.com/protocolbuffers/protobuf/issues/2679 + for rule_class in stringy_numbers.STRINGY_NUMBER_RULES: + self.register(rule_class._proto_type, rule_class()) + def to_python(self, proto_type, value, *, absent: bool = None): # Internal protobuf has its own special type for lists of values. # Return a view around it that implements MutableSequence. diff --git a/packages/proto-plus/proto/marshal/rules/message.py b/packages/proto-plus/proto/marshal/rules/message.py index e5ecf17ba632..c865b99d99d9 100644 --- a/packages/proto-plus/proto/marshal/rules/message.py +++ b/packages/proto-plus/proto/marshal/rules/message.py @@ -29,7 +29,16 @@ def to_proto(self, value): if isinstance(value, self._wrapper): return self._wrapper.pb(value) if isinstance(value, dict) and not self.is_map: - return self._descriptor(**value) + # We need to use the wrapper's marshaling to handle + # potentially problematic nested messages. + try: + # Try the fast path first. + return self._descriptor(**value) + except TypeError as ex: + # If we have a type error, + # try the slow path in case the error + # was an int64/string issue + return self._wrapper(value)._pb return value @property diff --git a/packages/proto-plus/proto/marshal/rules/stringy_numbers.py b/packages/proto-plus/proto/marshal/rules/stringy_numbers.py new file mode 100644 index 000000000000..0d808cc23d48 --- /dev/null +++ b/packages/proto-plus/proto/marshal/rules/stringy_numbers.py @@ -0,0 +1,68 @@ +# Copyright (C) 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from proto.primitives import ProtoType + + +class StringyNumberRule: + """A marshal between certain numeric types and strings + + This is a necessary hack to allow round trip conversion + from messages to dicts back to messages. + + See https://github.com/protocolbuffers/protobuf/issues/2679 + and + https://developers.google.com/protocol-buffers/docs/proto3#json + for more details. + """ + + def to_python(self, value, *, absent: bool = None): + return value + + def to_proto(self, value): + return self._python_type(value) + + +class Int64Rule(StringyNumberRule): + _python_type = int + _proto_type = ProtoType.INT64 + + +class UInt64Rule(StringyNumberRule): + _python_type = int + _proto_type = ProtoType.UINT64 + + +class SInt64Rule(StringyNumberRule): + _python_type = int + _proto_type = ProtoType.SINT64 + + +class Fixed64Rule(StringyNumberRule): + _python_type = int + _proto_type = ProtoType.FIXED64 + + +class SFixed64Rule(StringyNumberRule): + _python_type = int + _proto_type = ProtoType.SFIXED64 + + +STRINGY_NUMBER_RULES = [ + Int64Rule, + UInt64Rule, + SInt64Rule, + Fixed64Rule, + SFixed64Rule, +] diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 00ec4cc7c860..3dd224144b4f 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -394,7 +394,7 @@ def to_dict( 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 - determines whether the default field values should be included in the results. + determines whether the default field values should be included in the results. Default is True. Returns: @@ -453,7 +453,9 @@ class Message(metaclass=MessageMeta): message. """ - def __init__(self, mapping=None, *, ignore_unknown_fields=False, **kwargs): + def __init__( + self, mapping=None, *, ignore_unknown_fields=False, **kwargs, + ): # We accept several things for `mapping`: # * An instance of this class. # * An instance of the underlying protobuf descriptor class. diff --git a/packages/proto-plus/tests/test_fields_int.py b/packages/proto-plus/tests/test_fields_int.py index c3a979c052ca..40f5aa38eac3 100644 --- a/packages/proto-plus/tests/test_fields_int.py +++ b/packages/proto-plus/tests/test_fields_int.py @@ -93,3 +93,46 @@ class Foo(proto.Message): bar_field = Foo.meta.fields["bar"] assert bar_field.descriptor is bar_field.descriptor + + +def test_int64_dict_round_trip(): + # When converting a message to other types, protobuf turns int64 fields + # into decimal coded strings. + # This is not a problem for round trip JSON, but it is a problem + # when doing a round trip conversion from a message to a dict to a message. + # See https://github.com/protocolbuffers/protobuf/issues/2679 + # and + # https://developers.google.com/protocol-buffers/docs/proto3#json + # for more details. + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT64, number=1) + length_cm = proto.Field(proto.UINT64, number=2) + age_s = proto.Field(proto.FIXED64, number=3) + depth_m = proto.Field(proto.SFIXED64, number=4) + serial_num = proto.Field(proto.SINT64, number=5) + + s = Squid(mass_kg=10, length_cm=20, age_s=30, depth_m=40, serial_num=50) + + s_dict = Squid.to_dict(s) + + s2 = Squid(s_dict) + + assert s == s2 + + # Double check that the conversion works with deeply nested messages. + class Clam(proto.Message): + class Shell(proto.Message): + class Pearl(proto.Message): + mass_kg = proto.Field(proto.INT64, number=1) + + pearl = proto.Field(Pearl, number=1) + + shell = proto.Field(Shell, number=1) + + c = Clam(shell=Clam.Shell(pearl=Clam.Shell.Pearl(mass_kg=10))) + + c_dict = Clam.to_dict(c) + + c2 = Clam(c_dict) + + assert c == c2 From ee6055379a63e82c2a685e8ad565013319ca4550 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 25 Oct 2021 11:57:39 -0700 Subject: [PATCH 150/272] chore: release 1.19.6 (#268) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 4e6505f08fea..08e1b7c7ec63 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.6](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.5...v1.19.6) (2021-10-25) + + +### Bug Fixes + +* setting 64bit fields from strings supported ([#267](https://www.github.com/googleapis/proto-plus-python/issues/267)) ([ea7b911](https://www.github.com/googleapis/proto-plus-python/commit/ea7b91100114f5c3d40d41320b045568ac9a68f9)) + ### [1.19.5](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.4...v1.19.5) (2021-10-11) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 0aba6a545cee..3fe0b25a8541 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.5" +version = "1.19.6" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From ee089fa5492848350c5f5e37850e3691e463b92a Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Wed, 27 Oct 2021 09:12:01 -0700 Subject: [PATCH 151/272] test: verify json_name is respected (#270) Fields can customize their json name representation. Add a test to verify that this customization is respected by both Python and C++ runtimes. Boost protobuf requirement to gain required upstream bugfix. --- packages/proto-plus/noxfile.py | 16 +++++++++++++--- packages/proto-plus/setup.py | 2 +- packages/proto-plus/testing/constraints-3.6.txt | 1 - packages/proto-plus/tests/test_json.py | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 35d937d3b080..0e737e7373ad 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -22,7 +22,16 @@ CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() -@nox.session(python=["3.6", "3.7", "3.8", "3.9"]) +PYTHON_VERSIONS = [ + "3.6", + "3.7", + "3.8", + "3.9", + "3.10", +] + + +@nox.session(python=PYTHON_VERSIONS) def unit(session, proto="python"): """Run the unit test suite.""" @@ -54,12 +63,13 @@ 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"]) +@nox.session(python=PYTHON_VERSIONS) def unitcpp(session): return unit(session, proto="cpp") -@nox.session(python="3.9") +# Just use the most recent version for docs +@nox.session(python=PYTHON_VERSIONS[-1]) def docs(session): """Build the docs.""" diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 3fe0b25a8541..6e00f5fc6726 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -36,7 +36,7 @@ long_description=README, platforms="Posix; MacOS X", include_package_data=True, - install_requires=("protobuf >= 3.12.0",), + install_requires=("protobuf >= 3.19.0",), extras_require={"testing": ["google-api-core[grpc] >= 1.22.2",],}, python_requires=">=3.6", classifiers=[ diff --git a/packages/proto-plus/testing/constraints-3.6.txt b/packages/proto-plus/testing/constraints-3.6.txt index 31111ae4ffaf..d2ae7c5d45b3 100644 --- a/packages/proto-plus/testing/constraints-3.6.txt +++ b/packages/proto-plus/testing/constraints-3.6.txt @@ -5,5 +5,4 @@ # # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 -protobuf==3.15.6 google-api-core==1.22.2 diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index d1cbc1fb50f6..8faa96d4c7db 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -148,3 +148,17 @@ class Squid(proto.Message): assert s.mass_kg == 20 assert Squid.to_json(s, preserving_proto_field_name=True) == json_str + + +def test_json_name(): + class Squid(proto.Message): + massKg = proto.Field(proto.INT32, number=1, json_name="mass_in_kilograms") + + s = Squid(massKg=20) + j = Squid.to_json(s) + + assert "mass_in_kilograms" in j + + s_two = Squid.from_json(j) + + assert s == s_two From 1a00167c97fbb18a75f57e54f92c297ec3ed303a Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 27 Oct 2021 12:38:39 -0400 Subject: [PATCH 152/272] fix: restore allowing None as value for stringy ints (#272) Closes #271. --- .../proto/marshal/rules/stringy_numbers.py | 5 +- .../tests/test_marshal_stringy_numbers.py | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 packages/proto-plus/tests/test_marshal_stringy_numbers.py diff --git a/packages/proto-plus/proto/marshal/rules/stringy_numbers.py b/packages/proto-plus/proto/marshal/rules/stringy_numbers.py index 0d808cc23d48..dae69e9c9121 100644 --- a/packages/proto-plus/proto/marshal/rules/stringy_numbers.py +++ b/packages/proto-plus/proto/marshal/rules/stringy_numbers.py @@ -31,7 +31,10 @@ def to_python(self, value, *, absent: bool = None): return value def to_proto(self, value): - return self._python_type(value) + if value is not None: + return self._python_type(value) + + return None class Int64Rule(StringyNumberRule): diff --git a/packages/proto-plus/tests/test_marshal_stringy_numbers.py b/packages/proto-plus/tests/test_marshal_stringy_numbers.py new file mode 100644 index 000000000000..f3f40831dae6 --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_stringy_numbers.py @@ -0,0 +1,50 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from proto.marshal.marshal import BaseMarshal +from proto.primitives import ProtoType + +INT_32BIT_PLUS_ONE = 0xFFFFFFFF + 1 + + +@pytest.mark.parametrize( + "pb_type,value,expected", + [ + (ProtoType.INT64, 0, 0), + (ProtoType.INT64, INT_32BIT_PLUS_ONE, INT_32BIT_PLUS_ONE), + (ProtoType.SINT64, -INT_32BIT_PLUS_ONE, -INT_32BIT_PLUS_ONE), + (ProtoType.INT64, None, None), + (ProtoType.UINT64, 0, 0), + (ProtoType.UINT64, INT_32BIT_PLUS_ONE, INT_32BIT_PLUS_ONE), + (ProtoType.UINT64, None, None), + (ProtoType.SINT64, 0, 0), + (ProtoType.SINT64, INT_32BIT_PLUS_ONE, INT_32BIT_PLUS_ONE), + (ProtoType.SINT64, -INT_32BIT_PLUS_ONE, -INT_32BIT_PLUS_ONE), + (ProtoType.SINT64, None, None), + (ProtoType.FIXED64, 0, 0), + (ProtoType.FIXED64, INT_32BIT_PLUS_ONE, INT_32BIT_PLUS_ONE), + (ProtoType.FIXED64, -INT_32BIT_PLUS_ONE, -INT_32BIT_PLUS_ONE), + (ProtoType.FIXED64, None, None), + (ProtoType.SFIXED64, 0, 0), + (ProtoType.SFIXED64, INT_32BIT_PLUS_ONE, INT_32BIT_PLUS_ONE), + (ProtoType.SFIXED64, -INT_32BIT_PLUS_ONE, -INT_32BIT_PLUS_ONE), + (ProtoType.SFIXED64, None, None), + ], +) +def test_marshal_to_proto_stringy_numbers(pb_type, value, expected): + + marshal = BaseMarshal() + assert marshal.to_proto(pb_type, value) == expected From 8fa20c9026ca5fc8c9ae59a386e65e144a5558ca Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 09:41:43 -0700 Subject: [PATCH 153/272] chore: release 1.19.7 (#274) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 08e1b7c7ec63..715898801e70 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.7](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.6...v1.19.7) (2021-10-27) + + +### Bug Fixes + +* restore allowing None as value for stringy ints ([#272](https://www.github.com/googleapis/proto-plus-python/issues/272)) ([a8991d7](https://www.github.com/googleapis/proto-plus-python/commit/a8991d71ff455093fbfef142f9140d3f2928195f)) + ### [1.19.6](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.5...v1.19.6) (2021-10-25) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 6e00f5fc6726..406fcb124a77 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.6" +version = "1.19.7" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 12652fb7777c0618dd8c2bec0a383181f21e0ee5 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 27 Oct 2021 13:06:09 -0400 Subject: [PATCH 154/272] chore: pin minimum supported protobuf version for testing (#273) See: https://github.com/googleapis/proto-plus-python/pull/270/files#r737644737 --- packages/proto-plus/testing/constraints-3.6.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/proto-plus/testing/constraints-3.6.txt b/packages/proto-plus/testing/constraints-3.6.txt index d2ae7c5d45b3..852feeef0753 100644 --- a/packages/proto-plus/testing/constraints-3.6.txt +++ b/packages/proto-plus/testing/constraints-3.6.txt @@ -6,3 +6,4 @@ # 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.22.2 +protobuf==3.19.0 From a73187f7d6dcd77386df845dc40f063d31eb3703 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Fri, 5 Nov 2021 04:47:36 -0700 Subject: [PATCH 155/272] build: use main branch (#276) --- packages/proto-plus/.github/sync-repo-settings.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index d5b332af5f45..7e74b00b12d5 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -2,7 +2,7 @@ rebaseMergeAllowed: false squashMergeAllowed: true mergeCommitAllowed: false branchProtectionRules: -- pattern: master +- pattern: main isAdminEnforced: true requiredStatusCheckContexts: - 'style-check' From 43b90c91674760f43e482224e43be9705f0d4f10 Mon Sep 17 00:00:00 2001 From: Kian Meng Ang Date: Tue, 9 Nov 2021 22:15:43 +0800 Subject: [PATCH 156/272] docs: fix typos (#277) --- packages/proto-plus/docs/messages.rst | 2 +- packages/proto-plus/proto/datetime_helpers.py | 4 ++-- packages/proto-plus/proto/marshal/rules/bytes.py | 2 +- packages/proto-plus/proto/message.py | 4 ++-- packages/proto-plus/tests/test_datetime_helpers.py | 2 +- packages/proto-plus/tests/test_fields_bytes.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index e83a2246a905..c723a0d07fea 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -129,7 +129,7 @@ just as if it were a regular python object. song.composer = composer composer.given_name = "Carl" - # 'composer' is STILL not a referene to song.composer. + # 'composer' is STILL not a reference to song.composer. assert song.composer.given_name == "Wilhelm" # It does work in reverse, though, diff --git a/packages/proto-plus/proto/datetime_helpers.py b/packages/proto-plus/proto/datetime_helpers.py index 66732ba5ecd7..d73af624341b 100644 --- a/packages/proto-plus/proto/datetime_helpers.py +++ b/packages/proto-plus/proto/datetime_helpers.py @@ -63,7 +63,7 @@ def _to_rfc3339(value, ignore_zone=True): datetime object is ignored and the datetime is treated as UTC. Returns: - str: The RFC3339 formated string representing the datetime. + str: The RFC3339 formatted string representing the datetime. """ if not ignore_zone and value.tzinfo is not None: # Convert to UTC and remove the time zone info. @@ -97,7 +97,7 @@ def replace(self, *args, **kw): new values by whichever keyword arguments are specified. For example, if d == date(2002, 12, 31), then d.replace(day=26) == date(2002, 12, 26). - NOTE: nanosecond and microsecond are mutually exclusive arguemnts. + NOTE: nanosecond and microsecond are mutually exclusive arguments. """ ms_provided = "microsecond" in kw diff --git a/packages/proto-plus/proto/marshal/rules/bytes.py b/packages/proto-plus/proto/marshal/rules/bytes.py index 9c0890aa7066..080b0a03d4a0 100644 --- a/packages/proto-plus/proto/marshal/rules/bytes.py +++ b/packages/proto-plus/proto/marshal/rules/bytes.py @@ -19,7 +19,7 @@ class BytesRule: """A marshal between Python strings and protobuf bytes. - Note: this conversion is asymmetric becasue Python does have a bytes type. + Note: this conversion is asymmetric because Python does have a bytes type. It is sometimes necessary to convert proto bytes fields to strings, e.g. for JSON encoding, marshalling a message to a dict. Because bytes fields can represent arbitrary data, bytes fields are base64 encoded when they need to diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 3dd224144b4f..d7a61dcb09cb 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -475,7 +475,7 @@ def __init__( # passed in. # # The `wrap` method on the metaclass is the public API for taking - # ownership of the passed in protobuf objet. + # ownership of the passed in protobuf object. mapping = copy.deepcopy(mapping) if kwargs: mapping.MergeFrom(self._meta.pb(**kwargs)) @@ -540,7 +540,7 @@ def __contains__(self, key): to get a boolean that distinguishes between ``False`` and ``None`` (or the same for a string, int, etc.). This library transparently handles that case for you, but this method remains available to - accomodate cases not automatically covered. + accommodate cases not automatically covered. Args: key (str): The name of the field. diff --git a/packages/proto-plus/tests/test_datetime_helpers.py b/packages/proto-plus/tests/test_datetime_helpers.py index 84001f92ac66..24bc935df66b 100644 --- a/packages/proto-plus/tests/test_datetime_helpers.py +++ b/packages/proto-plus/tests/test_datetime_helpers.py @@ -281,7 +281,7 @@ def _to_seconds(value): """Convert a datetime to seconds since the unix epoch. Args: - value (datetime.datetime): The datetime to covert. + value (datetime.datetime): The datetime to convert. Returns: int: Microseconds since the unix epoch. diff --git a/packages/proto-plus/tests/test_fields_bytes.py b/packages/proto-plus/tests/test_fields_bytes.py index a141c855bae4..625dcd91c404 100644 --- a/packages/proto-plus/tests/test_fields_bytes.py +++ b/packages/proto-plus/tests/test_fields_bytes.py @@ -77,7 +77,7 @@ class Foo(proto.Message): # but the marshalling needs to base64 decode the result. # This is a requirement for interop with the vanilla protobuf runtime: # converting a proto message to a dict base64 encodes the bytes - # becase it may be sent over the network via a protocol like HTTP. + # because it may be sent over the network via a protocol like HTTP. encoded_swallow: str = base64.urlsafe_b64encode(b"unladen swallow").decode("utf-8") assert type(encoded_swallow) == str foo.baz = encoded_swallow From f3b0cca4bdf747d889d5f3a9de550ee429963486 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 9 Nov 2021 10:47:08 -0800 Subject: [PATCH 157/272] chore: release 1.19.8 (#278) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 715898801e70..6a27ef5db50d 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.8](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.7...v1.19.8) (2021-11-09) + + +### Documentation + +* fix typos ([#277](https://www.github.com/googleapis/proto-plus-python/issues/277)) ([e3b71e8](https://www.github.com/googleapis/proto-plus-python/commit/e3b71e8b2a81a5abb5af666c9625facb1814a609)) + ### [1.19.7](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.6...v1.19.7) (2021-10-27) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 406fcb124a77..8902f67faffa 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.7" +version = "1.19.8" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From b27480256dd7603d9a3b128f907ce30d1b0bd4fe Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 9 Dec 2021 13:12:11 -0500 Subject: [PATCH 158/272] fix: add pickling support to proto messages (#280) FBO of frameworks such as Apache Beam, which use them for sharing state between trusted hosts. Closes #260. --- packages/proto-plus/docs/messages.rst | 7 ++- packages/proto-plus/proto/message.py | 9 ++++ .../proto-plus/tests/test_message_pickling.py | 51 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 packages/proto-plus/tests/test_message_pickling.py diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index c723a0d07fea..e033473c02f5 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -246,8 +246,11 @@ already allows construction from mapping types. .. note:: - Protobuf messages **CANNOT** be safely pickled or unpickled. This has serious consequences for programs that use multiprocessing or write messages to files. - The preferred mechanism for serializing proto messages is :meth:`~.Message.serialize`. + Although Python's pickling protocol has known issues when used with + untrusted collaborators, some frameworks do use it for communication + between trusted hosts. To support such frameworks, protobuf messages + **can** be pickled and unpickled, although the preferred mechanism for + serializing proto messages is :meth:`~.Message.serialize`. Multiprocessing example: diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index d7a61dcb09cb..97ee38146207 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -642,6 +642,15 @@ def __setattr__(self, key, value): if pb_value is not None: self._pb.MergeFrom(self._meta.pb(**{key: pb_value})) + def __getstate__(self): + """Serialize for pickling.""" + return self._pb.SerializeToString() + + def __setstate__(self, value): + """Deserialization for pickling.""" + new_pb = self._meta.pb().FromString(value) + super().__setattr__("_pb", new_pb) + class _MessageInfo: """Metadata about a message. diff --git a/packages/proto-plus/tests/test_message_pickling.py b/packages/proto-plus/tests/test_message_pickling.py new file mode 100644 index 000000000000..dd97403c1a79 --- /dev/null +++ b/packages/proto-plus/tests/test_message_pickling.py @@ -0,0 +1,51 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools +import pickle + +import pytest + +import proto + + +class Squid(proto.Message): + # Test primitives, enums, and repeated fields. + class Chromatophore(proto.Message): + class Color(proto.Enum): + UNKNOWN = 0 + RED = 1 + BROWN = 2 + WHITE = 3 + BLUE = 4 + + color = proto.Field(Color, number=1) + + mass_kg = proto.Field(proto.INT32, number=1) + chromatophores = proto.RepeatedField(Chromatophore, number=2) + + +def test_pickling(): + + s = Squid(mass_kg=20) + colors = ["RED", "BROWN", "WHITE", "BLUE"] + s.chromatophores = [ + {"color": c} for c in itertools.islice(itertools.cycle(colors), 10) + ] + + pickled = pickle.dumps(s) + + unpickled = pickle.loads(pickled) + + assert unpickled == s From bb622d372a7f7f8a9d2febec18418f5bba805cb3 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 25 Jan 2022 23:41:21 +0100 Subject: [PATCH 159/272] chore(deps): update styfle/cancel-workflow-action action to v0.9.1 (#286) --- packages/proto-plus/.github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 93d7308af09a..282fe7fa6d16 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.0 + uses: styfle/cancel-workflow-action@0.9.1 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.0 + uses: styfle/cancel-workflow-action@0.9.1 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 @@ -50,7 +50,7 @@ jobs: variant: ['', cpp] steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.0 + uses: styfle/cancel-workflow-action@0.9.1 with: access_token: ${{ github.token }} - uses: actions/checkout@v2 From e6b241d0ff435602140117046fb665409ab38d3e Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 25 Jan 2022 22:46:12 +0000 Subject: [PATCH 160/272] chore(main): release 1.19.9 (#287) :robot: I have created a release *beep* *boop* --- ### [1.19.9](https://github.com/googleapis/proto-plus-python/compare/v1.19.8...v1.19.9) (2022-01-25) ### Bug Fixes * add pickling support to proto messages ([#280](https://github.com/googleapis/proto-plus-python/issues/280)) ([2b7be35](https://github.com/googleapis/proto-plus-python/commit/2b7be3563f9fc2a4649a5e14d7653b85020c566f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 6a27ef5db50d..bab591adf86a 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.19.9](https://github.com/googleapis/proto-plus-python/compare/v1.19.8...v1.19.9) (2022-01-25) + + +### Bug Fixes + +* add pickling support to proto messages ([#280](https://github.com/googleapis/proto-plus-python/issues/280)) ([2b7be35](https://github.com/googleapis/proto-plus-python/commit/2b7be3563f9fc2a4649a5e14d7653b85020c566f)) + ### [1.19.8](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.7...v1.19.8) (2021-11-09) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 8902f67faffa..fa822af656ef 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.8" +version = "1.19.9" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From e7cccddf189b95e1d6f016a338486dc9b902bff0 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 7 Feb 2022 12:13:03 -0800 Subject: [PATCH 161/272] fix: workaround for buggy pytest (#291) --- packages/proto-plus/noxfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 0e737e7373ad..9b957383c790 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -40,7 +40,8 @@ def unit(session, proto="python"): ) session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto - session.install("coverage", "pytest", "pytest-cov", "pytz") + # Pytest 7.0.0 is buggy + session.install("coverage", "pytest<7.0.0", "pytest-cov", "pytz") session.install("-e", ".[testing]", "-c", constraints_path) session.run( From 6db28010e8e92bfdc92a25cf1a96a03c2f42d852 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Mon, 7 Feb 2022 12:15:34 -0800 Subject: [PATCH 162/272] feat: add custom __dir__ for messages and message classes (#289) During development, it can be convenient to inspect objects and types directly to determine what methods and attributes they have using the dir() builtin command in a debugger or a REPL. Because proto-plus messages wrap their fields using __getattr__, the proto fields are not visible by default and must be explicitly exposed to dir(). --- packages/proto-plus/proto/message.py | 44 +++++++++++++++ packages/proto-plus/tests/test_message.py | 69 +++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 97ee38146207..5135a542d1d7 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -273,6 +273,27 @@ def __prepare__(mcls, name, bases, **kwargs): def meta(cls): return cls._meta + def __dir__(self): + names = set(dir(type)) + names.update( + ( + "meta", + "pb", + "wrap", + "serialize", + "deserialize", + "to_json", + "from_json", + "to_dict", + "copy_from", + ) + ) + desc = self.pb().DESCRIPTOR + names.update(t.name for t in desc.nested_types) + names.update(e.name for e in desc.enum_types) + + return names + def pb(cls, obj=None, *, coerce: bool = False): """Return the underlying protobuf Message class or instance. @@ -520,6 +541,29 @@ def __init__( # Create the internal protocol buffer. super().__setattr__("_pb", self._meta.pb(**params)) + def __dir__(self): + desc = type(self).pb().DESCRIPTOR + names = {f_name for f_name in self._meta.fields.keys()} + names.update(m.name for m in desc.nested_types) + names.update(e.name for e in desc.enum_types) + names.update(dir(object())) + # Can't think of a better way of determining + # the special methods than manually listing them. + names.update( + ( + "__bool__", + "__contains__", + "__dict__", + "__getattr__", + "__getstate__", + "__module__", + "__setstate__", + "__weakref__", + ) + ) + + return names + def __bool__(self): """Return True if any field is truthy, False otherwise.""" return any(k in self and getattr(self, k) for k in self._meta.fields.keys()) diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index 5351dbd77165..843fad22b295 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -346,3 +346,72 @@ class Squid(proto.Message): with pytest.raises(TypeError): Mollusc.Squid.copy_from(m.squid, (("mass_kg", 20))) + + +def test_dir(): + class Mollusc(proto.Message): + class Class(proto.Enum): + UNKNOWN = 0 + GASTROPOD = 1 + BIVALVE = 2 + CEPHALOPOD = 3 + + class Arm(proto.Message): + length_cm = proto.Field(proto.INT32, number=1) + + mass_kg = proto.Field(proto.INT32, number=1) + class_ = proto.Field(Class, number=2) + arms = proto.RepeatedField(Arm, number=3) + + expected = ( + { + # Fields and nested message and enum types + "arms", + "class_", + "mass_kg", + "Arm", + "Class", + } + | { + # Other methods and attributes + "__bool__", + "__contains__", + "__dict__", + "__getattr__", + "__getstate__", + "__module__", + "__setstate__", + "__weakref__", + } + | set(dir(object)) + ) # Gets the long tail of dunder methods and attributes. + + actual = set(dir(Mollusc())) + + # Check instance names + assert actual == expected + + # Check type names + expected = ( + set(dir(type)) + | { + # Class methods from the MessageMeta metaclass + "copy_from", + "deserialize", + "from_json", + "meta", + "pb", + "serialize", + "to_dict", + "to_json", + "wrap", + } + | { + # Nested message and enum types + "Arm", + "Class", + } + ) + + actual = set(dir(Mollusc)) + assert actual == expected From d90c5a0679d2c02625ff9f333c2e13ceebe576e3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 20:16:49 +0000 Subject: [PATCH 163/272] chore(main): release 1.20.0 (#292) :robot: I have created a release *beep* *boop* --- ## [1.20.0](https://github.com/googleapis/proto-plus-python/compare/v1.19.9...v1.20.0) (2022-02-07) ### Features * add custom __dir__ for messages and message classes ([#289](https://github.com/googleapis/proto-plus-python/issues/289)) ([35e019e](https://github.com/googleapis/proto-plus-python/commit/35e019eb8155c1e4067b326804e3e7e86f85b6a8)) ### Bug Fixes * workaround for buggy pytest ([#291](https://github.com/googleapis/proto-plus-python/issues/291)) ([28aa3b2](https://github.com/googleapis/proto-plus-python/commit/28aa3b2b325d2ba262f35cfc8d20e1f5fbdcf883)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/proto-plus/CHANGELOG.md | 12 ++++++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index bab591adf86a..04c908965412 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [1.20.0](https://github.com/googleapis/proto-plus-python/compare/v1.19.9...v1.20.0) (2022-02-07) + + +### Features + +* add custom __dir__ for messages and message classes ([#289](https://github.com/googleapis/proto-plus-python/issues/289)) ([35e019e](https://github.com/googleapis/proto-plus-python/commit/35e019eb8155c1e4067b326804e3e7e86f85b6a8)) + + +### Bug Fixes + +* workaround for buggy pytest ([#291](https://github.com/googleapis/proto-plus-python/issues/291)) ([28aa3b2](https://github.com/googleapis/proto-plus-python/commit/28aa3b2b325d2ba262f35cfc8d20e1f5fbdcf883)) + ### [1.19.9](https://github.com/googleapis/proto-plus-python/compare/v1.19.8...v1.19.9) (2022-01-25) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index fa822af656ef..77508bba4c05 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.19.9" +version = "1.20.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 76211c2647fb93f00424594129ba5653df9fa035 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Tue, 8 Feb 2022 02:38:02 -0800 Subject: [PATCH 164/272] test: remove extraneous @staticmethod in test file (#293) This is what was causing the Internal Error with pytest 7.0.0 --- packages/proto-plus/noxfile.py | 3 +-- packages/proto-plus/tests/test_datetime_helpers.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 9b957383c790..0e737e7373ad 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -40,8 +40,7 @@ def unit(session, proto="python"): ) session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto - # Pytest 7.0.0 is buggy - session.install("coverage", "pytest<7.0.0", "pytest-cov", "pytz") + session.install("coverage", "pytest", "pytest-cov", "pytz") session.install("-e", ".[testing]", "-c", constraints_path) session.run( diff --git a/packages/proto-plus/tests/test_datetime_helpers.py b/packages/proto-plus/tests/test_datetime_helpers.py index 24bc935df66b..264b5296980a 100644 --- a/packages/proto-plus/tests/test_datetime_helpers.py +++ b/packages/proto-plus/tests/test_datetime_helpers.py @@ -173,7 +173,6 @@ def test_from_rfc3339_w_full_precision(): assert stamp == expected -@staticmethod @pytest.mark.parametrize( "fractional, nanos", [ From b8a3f9621137fb9e7dd8af0e013033addd6d4af2 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 14 Feb 2022 14:48:16 -0500 Subject: [PATCH 165/272] fix: mitigate collisions in field names (#295) * fix: mitigate collisions in field names * lint * add comment * address review feedback --- packages/proto-plus/proto/message.py | 23 ++++++++---- .../tests/test_fields_mitigate_collision.py | 36 +++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 packages/proto-plus/tests/test_fields_mitigate_collision.py diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 5135a542d1d7..7293fdb5bd2a 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -527,12 +527,23 @@ def __init__( try: pb_type = self._meta.fields[key].pb_type except KeyError: - if ignore_unknown_fields: - continue - - raise ValueError( - "Unknown field for {}: {}".format(self.__class__.__name__, key) - ) + # Underscores may be appended to field names + # that collide with python or proto-plus keywords. + # In case a key only exists with a `_` suffix, coerce the key + # to include the `_` suffix. Is not possible to + # natively define the same field with a trailing underscore in protobuf. + # See related issue + # https://github.com/googleapis/python-api-core/issues/227 + if f"{key}_" in self._meta.fields: + key = f"{key}_" + pb_type = self._meta.fields[key].pb_type + else: + if ignore_unknown_fields: + continue + + raise ValueError( + "Unknown field for {}: {}".format(self.__class__.__name__, key) + ) pb_value = marshal.to_proto(pb_type, value) if pb_value is not None: diff --git a/packages/proto-plus/tests/test_fields_mitigate_collision.py b/packages/proto-plus/tests/test_fields_mitigate_collision.py new file mode 100644 index 000000000000..0dca71df0b51 --- /dev/null +++ b/packages/proto-plus/tests/test_fields_mitigate_collision.py @@ -0,0 +1,36 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +# Underscores may be appended to field names +# that collide with python or proto-plus keywords. +# In case a key only exists with a `_` suffix, coerce the key +# to include the `_` suffix. Is not possible to +# natively define the same field with a trailing underscore in protobuf. +# See related issue +# https://github.com/googleapis/python-api-core/issues/227 +def test_fields_mitigate_collision(): + class TestMessage(proto.Message): + spam_ = proto.Field(proto.STRING, number=1) + eggs = proto.Field(proto.STRING, number=2) + + obj = TestMessage(spam_="has_spam") + obj.eggs = "has_eggs" + assert obj.spam_ == "has_spam" + + # Test that `spam` is coerced to `spam_` + modified_obj = TestMessage({"spam": "has_spam", "eggs": "has_eggs"}) + assert modified_obj.spam_ == "has_spam" From a4c1a03ac05c0c29cbb9c9b46f767f179306f5f0 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 14:53:24 -0500 Subject: [PATCH 166/272] chore(main): release 1.20.1 (#297) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 04c908965412..ba85476fc81f 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.20.1](https://github.com/googleapis/proto-plus-python/compare/v1.20.0...v1.20.1) (2022-02-14) + + +### Bug Fixes + +* mitigate collisions in field names ([#295](https://github.com/googleapis/proto-plus-python/issues/295)) ([158ae99](https://github.com/googleapis/proto-plus-python/commit/158ae995aa4fdf6239c864a41f5df5575a3c30b3)) + ## [1.20.0](https://github.com/googleapis/proto-plus-python/compare/v1.19.9...v1.20.0) (2022-02-07) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 77508bba4c05..cf76c368485b 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.20.0" +version = "1.20.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 746719e0d2658c26a741b1e0f0bb8e31ba080a31 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 14 Feb 2022 16:01:39 -0500 Subject: [PATCH 167/272] chore: allow release-please to make releases (#298) --- packages/proto-plus/.github/release-please.yml | 1 + packages/proto-plus/.github/release-trigger.yml | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 packages/proto-plus/.github/release-trigger.yml diff --git a/packages/proto-plus/.github/release-please.yml b/packages/proto-plus/.github/release-please.yml index 4507ad0598a5..466597e5b196 100644 --- a/packages/proto-plus/.github/release-please.yml +++ b/packages/proto-plus/.github/release-please.yml @@ -1 +1,2 @@ releaseType: python +handleGHRelease: true diff --git a/packages/proto-plus/.github/release-trigger.yml b/packages/proto-plus/.github/release-trigger.yml new file mode 100644 index 000000000000..1e9cfcd3a5de --- /dev/null +++ b/packages/proto-plus/.github/release-trigger.yml @@ -0,0 +1,2 @@ +enabled: true + From 0798289e82255d2fa961633326c4b19166bbdb62 Mon Sep 17 00:00:00 2001 From: Anurag Kumar Date: Tue, 15 Feb 2022 04:45:01 +0530 Subject: [PATCH 168/272] chore: add classifier for python 3.10 (#283) Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/tests.yml | 2 +- packages/proto-plus/setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 282fe7fa6d16..eb1c51d29b93 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9] + python: ['3.6', '3.7', '3.8', '3.9', '3.10'] variant: ['', cpp] steps: - name: Cancel Previous Runs diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index cf76c368485b..a259f8bf5830 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -50,6 +50,7 @@ "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", ], From 4dcc8b250ca3c8d72c998eaec561d90c9f759802 Mon Sep 17 00:00:00 2001 From: Dov Shlachter Date: Thu, 17 Feb 2022 10:29:44 -0800 Subject: [PATCH 169/272] fix: dir(proto.Message) does not raise (#302) dir(proto.Message) defaults to dir(type) --- packages/proto-plus/proto/message.py | 37 ++++++++++++----------- packages/proto-plus/tests/test_message.py | 4 +++ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 7293fdb5bd2a..de9280dae448 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -274,25 +274,28 @@ def meta(cls): return cls._meta def __dir__(self): - names = set(dir(type)) - names.update( - ( - "meta", - "pb", - "wrap", - "serialize", - "deserialize", - "to_json", - "from_json", - "to_dict", - "copy_from", + try: + names = set(dir(type)) + names.update( + ( + "meta", + "pb", + "wrap", + "serialize", + "deserialize", + "to_json", + "from_json", + "to_dict", + "copy_from", + ) ) - ) - desc = self.pb().DESCRIPTOR - names.update(t.name for t in desc.nested_types) - names.update(e.name for e in desc.enum_types) + desc = self.pb().DESCRIPTOR + names.update(t.name for t in desc.nested_types) + names.update(e.name for e in desc.enum_types) - return names + return names + except AttributeError: + return dir(type) def pb(cls, obj=None, *, coerce: bool = False): """Return the underlying protobuf Message class or instance. diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index 843fad22b295..3146f0bb5a4b 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -415,3 +415,7 @@ class Arm(proto.Message): actual = set(dir(Mollusc)) assert actual == expected + + +def test_dir_message_base(): + assert set(dir(proto.Message)) == set(dir(type)) From 989902c74aad56b31369b9078ed2c7106830bf94 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 17 Feb 2022 11:58:35 -0800 Subject: [PATCH 170/272] chore(main): release 1.20.2 (#303) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index ba85476fc81f..ffc8a7c9ba17 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.20.2](https://github.com/googleapis/proto-plus-python/compare/v1.20.1...v1.20.2) (2022-02-17) + + +### Bug Fixes + +* dir(proto.Message) does not raise ([#302](https://github.com/googleapis/proto-plus-python/issues/302)) ([80dcce9](https://github.com/googleapis/proto-plus-python/commit/80dcce9099e630a6217792b6b3a14213add690e6)) + ### [1.20.1](https://github.com/googleapis/proto-plus-python/compare/v1.20.0...v1.20.1) (2022-02-14) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index a259f8bf5830..109d0b90d421 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.20.1" +version = "1.20.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 9a2a1ee62407c9f525b247175b0d2d0c2922a9ec Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 17 Feb 2022 19:37:47 -0500 Subject: [PATCH 171/272] fix: additional logic to mitigate collisions with reserved terms (#301) * fix: additional logic to mitigate collisions with reserved terms * address review feedback * remove line break * remove redundant code * add assert --- packages/proto-plus/proto/message.py | 90 ++++++++++++++----- .../tests/test_fields_mitigate_collision.py | 49 +++++++++- 2 files changed, 114 insertions(+), 25 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index de9280dae448..13a7635c3a7f 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -527,34 +527,72 @@ def __init__( # coerced. marshal = self._meta.marshal for key, value in mapping.items(): + (key, pb_type) = self._get_pb_type_from_key(key) + if pb_type is None: + if ignore_unknown_fields: + continue + + raise ValueError( + "Unknown field for {}: {}".format(self.__class__.__name__, key) + ) + try: - pb_type = self._meta.fields[key].pb_type - except KeyError: + pb_value = marshal.to_proto(pb_type, value) + except ValueError: # Underscores may be appended to field names # that collide with python or proto-plus keywords. # In case a key only exists with a `_` suffix, coerce the key - # to include the `_` suffix. Is not possible to + # to include the `_` suffix. It's not possible to # natively define the same field with a trailing underscore in protobuf. # See related issue # https://github.com/googleapis/python-api-core/issues/227 - if f"{key}_" in self._meta.fields: - key = f"{key}_" - pb_type = self._meta.fields[key].pb_type - else: - if ignore_unknown_fields: - continue - - raise ValueError( - "Unknown field for {}: {}".format(self.__class__.__name__, key) - ) - - pb_value = marshal.to_proto(pb_type, value) + if isinstance(value, dict): + keys_to_update = [ + item + for item in value + if not hasattr(pb_type, item) and hasattr(pb_type, f"{item}_") + ] + for item in keys_to_update: + value[f"{item}_"] = value.pop(item) + + pb_value = marshal.to_proto(pb_type, value) + if pb_value is not None: params[key] = pb_value # Create the internal protocol buffer. super().__setattr__("_pb", self._meta.pb(**params)) + def _get_pb_type_from_key(self, key): + """Given a key, return the corresponding pb_type. + + Args: + key(str): The name of the field. + + Returns: + A tuple containing a key and pb_type. The pb_type will be + the composite type of the field, or the primitive type if a primitive. + If no corresponding field exists, return None. + """ + + pb_type = None + + try: + pb_type = self._meta.fields[key].pb_type + except KeyError: + # Underscores may be appended to field names + # that collide with python or proto-plus keywords. + # In case a key only exists with a `_` suffix, coerce the key + # to include the `_` suffix. It's not possible to + # natively define the same field with a trailing underscore in protobuf. + # See related issue + # https://github.com/googleapis/python-api-core/issues/227 + if f"{key}_" in self._meta.fields: + key = f"{key}_" + pb_type = self._meta.fields[key].pb_type + + return (key, pb_type) + def __dir__(self): desc = type(self).pb().DESCRIPTOR names = {f_name for f_name in self._meta.fields.keys()} @@ -664,13 +702,14 @@ def __getattr__(self, key): their Python equivalents. See the ``marshal`` module for more details. """ - try: - pb_type = self._meta.fields[key].pb_type - pb_value = getattr(self._pb, key) - marshal = self._meta.marshal - return marshal.to_python(pb_type, pb_value, absent=key not in self) - except KeyError as ex: - raise AttributeError(str(ex)) + (key, pb_type) = self._get_pb_type_from_key(key) + if pb_type is None: + raise AttributeError( + "Unknown field for {}: {}".format(self.__class__.__name__, key) + ) + pb_value = getattr(self._pb, key) + marshal = self._meta.marshal + return marshal.to_python(pb_type, pb_value, absent=key not in self) def __ne__(self, other): """Return True if the messages are unequal, False otherwise.""" @@ -688,7 +727,12 @@ def __setattr__(self, key, value): if key[0] == "_": return super().__setattr__(key, value) marshal = self._meta.marshal - pb_type = self._meta.fields[key].pb_type + (key, pb_type) = self._get_pb_type_from_key(key) + if pb_type is None: + raise AttributeError( + "Unknown field for {}: {}".format(self.__class__.__name__, key) + ) + pb_value = marshal.to_proto(pb_type, value) # Clear the existing field. diff --git a/packages/proto-plus/tests/test_fields_mitigate_collision.py b/packages/proto-plus/tests/test_fields_mitigate_collision.py index 0dca71df0b51..117af48ac241 100644 --- a/packages/proto-plus/tests/test_fields_mitigate_collision.py +++ b/packages/proto-plus/tests/test_fields_mitigate_collision.py @@ -13,12 +13,12 @@ # limitations under the License. import proto - +import pytest # Underscores may be appended to field names # that collide with python or proto-plus keywords. # In case a key only exists with a `_` suffix, coerce the key -# to include the `_` suffix. Is not possible to +# to include the `_` suffix. It's not possible to # natively define the same field with a trailing underscore in protobuf. # See related issue # https://github.com/googleapis/python-api-core/issues/227 @@ -27,6 +27,9 @@ class TestMessage(proto.Message): spam_ = proto.Field(proto.STRING, number=1) eggs = proto.Field(proto.STRING, number=2) + class TextStream(proto.Message): + text_stream = proto.Field(TestMessage, number=1) + obj = TestMessage(spam_="has_spam") obj.eggs = "has_eggs" assert obj.spam_ == "has_spam" @@ -34,3 +37,45 @@ class TestMessage(proto.Message): # Test that `spam` is coerced to `spam_` modified_obj = TestMessage({"spam": "has_spam", "eggs": "has_eggs"}) assert modified_obj.spam_ == "has_spam" + + # Test get and set + modified_obj.spam = "no_spam" + assert modified_obj.spam == "no_spam" + + modified_obj.spam_ = "yes_spam" + assert modified_obj.spam_ == "yes_spam" + + modified_obj.spam = "maybe_spam" + assert modified_obj.spam_ == "maybe_spam" + + modified_obj.spam_ = "maybe_not_spam" + assert modified_obj.spam == "maybe_not_spam" + + # Try nested values + modified_obj = TextStream( + text_stream=TestMessage({"spam": "has_spam", "eggs": "has_eggs"}) + ) + assert modified_obj.text_stream.spam_ == "has_spam" + + # Test get and set for nested values + modified_obj.text_stream.spam = "no_spam" + assert modified_obj.text_stream.spam == "no_spam" + + modified_obj.text_stream.spam_ = "yes_spam" + assert modified_obj.text_stream.spam_ == "yes_spam" + + modified_obj.text_stream.spam = "maybe_spam" + assert modified_obj.text_stream.spam_ == "maybe_spam" + + modified_obj.text_stream.spam_ = "maybe_not_spam" + assert modified_obj.text_stream.spam == "maybe_not_spam" + + with pytest.raises(AttributeError): + assert modified_obj.text_stream.attribute_does_not_exist == "n/a" + + with pytest.raises(AttributeError): + modified_obj.text_stream.attribute_does_not_exist = "n/a" + + # Try using dict + modified_obj = TextStream(text_stream={"spam": "has_spam", "eggs": "has_eggs"}) + assert modified_obj.text_stream.spam_ == "has_spam" From ea01e1fb10d8c9befef24ef654961ae4cf7e221e Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 17 Feb 2022 19:20:55 -0800 Subject: [PATCH 172/272] chore(main): release 1.20.3 (#305) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index ffc8a7c9ba17..28b1d33ea625 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [1.20.3](https://github.com/googleapis/proto-plus-python/compare/v1.20.2...v1.20.3) (2022-02-18) + + +### Bug Fixes + +* additional logic to mitigate collisions with reserved terms ([#301](https://github.com/googleapis/proto-plus-python/issues/301)) ([c9a77df](https://github.com/googleapis/proto-plus-python/commit/c9a77df58e93a87952470d809538a08103644364)) + ### [1.20.2](https://github.com/googleapis/proto-plus-python/compare/v1.20.1...v1.20.2) (2022-02-17) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 109d0b90d421..a68e9856199c 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.20.2" +version = "1.20.3" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 399db506072586359a757ecfb38d31cd4fb36cd1 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 28 Feb 2022 19:11:45 +0100 Subject: [PATCH 173/272] chore(deps): update actions/setup-python action to v3 (#307) --- packages/proto-plus/.github/workflows/pypi-upload.yaml | 2 +- packages/proto-plus/.github/workflows/tests.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/.github/workflows/pypi-upload.yaml b/packages/proto-plus/.github/workflows/pypi-upload.yaml index 055453598572..b4151b687a73 100644 --- a/packages/proto-plus/.github/workflows/pypi-upload.yaml +++ b/packages/proto-plus/.github/workflows/pypi-upload.yaml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: '3.x' - name: Install dependencies diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index eb1c51d29b93..94f64c5552ae 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 - name: Install black @@ -35,7 +35,7 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 - name: Install nox. @@ -55,7 +55,7 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - name: Install nox and codecov. From 2c0035f6675059af311f9661147f31e6d443bcca Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 26 Apr 2022 14:53:40 -0400 Subject: [PATCH 174/272] chore: use black==22.3.0 (#310) --- .../proto-plus/.github/workflows/tests.yml | 2 +- packages/proto-plus/proto/_file_info.py | 4 +- packages/proto-plus/proto/datetime_helpers.py | 2 +- packages/proto-plus/proto/fields.py | 6 +- packages/proto-plus/proto/marshal/marshal.py | 3 +- .../proto-plus/proto/marshal/rules/dates.py | 3 +- .../proto-plus/proto/marshal/rules/enums.py | 3 +- .../proto-plus/proto/marshal/rules/struct.py | 12 +++- packages/proto-plus/proto/message.py | 37 ++++++++--- packages/proto-plus/proto/modules.py | 9 ++- packages/proto-plus/setup.py | 6 +- packages/proto-plus/tests/clam.py | 8 ++- packages/proto-plus/tests/conftest.py | 4 +- packages/proto-plus/tests/enums_test.py | 7 +- packages/proto-plus/tests/mollusc.py | 7 +- packages/proto-plus/tests/test_fields_int.py | 8 +-- .../tests/test_fields_map_composite.py | 35 ++++++++-- .../tests/test_fields_repeated_composite.py | 4 +- .../tests/test_file_info_salting.py | 4 +- .../test_file_info_salting_with_manifest.py | 9 ++- .../tests/test_marshal_types_dates.py | 64 +++++++++++++++---- .../tests/test_marshal_types_enum.py | 13 +++- .../tests/test_marshal_types_wrappers_bool.py | 30 +++++++-- ...sage_filename_with_and_without_manifest.py | 5 +- .../test_message_filename_with_manifest.py | 5 +- .../proto-plus/tests/test_message_nested.py | 3 +- packages/proto-plus/tests/test_modules.py | 8 ++- packages/proto-plus/tests/zone.py | 7 +- 28 files changed, 243 insertions(+), 65 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 94f64c5552ae..026e15c24c29 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: with: python-version: 3.8 - name: Install black - run: pip install black==19.10b0 + run: pip install black==22.3.0 - name: Check diff run: black --diff --check . docs: diff --git a/packages/proto-plus/proto/_file_info.py b/packages/proto-plus/proto/_file_info.py index 34ec79c5d719..537eeaf4556a 100644 --- a/packages/proto-plus/proto/_file_info.py +++ b/packages/proto-plus/proto/_file_info.py @@ -40,7 +40,9 @@ def maybe_add_descriptor(cls, filename, package): if not descriptor: descriptor = cls.registry[filename] = cls( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", + name=filename, + package=package, + syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), diff --git a/packages/proto-plus/proto/datetime_helpers.py b/packages/proto-plus/proto/datetime_helpers.py index d73af624341b..7a3aafec68ea 100644 --- a/packages/proto-plus/proto/datetime_helpers.py +++ b/packages/proto-plus/proto/datetime_helpers.py @@ -171,7 +171,7 @@ def from_rfc3339(cls, stamp): nanos = 0 else: scale = 9 - len(fraction) - nanos = int(fraction) * (10 ** scale) + nanos = int(fraction) * (10**scale) return cls( bare.year, bare.month, diff --git a/packages/proto-plus/proto/fields.py b/packages/proto-plus/proto/fields.py index b58349254c05..6f5b64521619 100644 --- a/packages/proto-plus/proto/fields.py +++ b/packages/proto-plus/proto/fields.py @@ -78,7 +78,8 @@ def descriptor(self): if isinstance(self.message, str): if not self.message.startswith(self.package): self.message = "{package}.{name}".format( - package=self.package, name=self.message, + package=self.package, + name=self.message, ) type_name = self.message elif self.message: @@ -90,7 +91,8 @@ def descriptor(self): elif isinstance(self.enum, str): if not self.enum.startswith(self.package): self.enum = "{package}.{name}".format( - package=self.package, name=self.enum, + package=self.package, + name=self.enum, ) type_name = self.enum elif self.enum: diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index baac7adcccc3..e7f8d4d92161 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -215,7 +215,8 @@ def to_proto(self, proto_type, value, *, strict: bool = False): raise TypeError( "Parameter must be instance of the same class; " "expected {expected}, got {got}".format( - expected=proto_type.__name__, got=pb_value.__class__.__name__, + expected=proto_type.__name__, + got=pb_value.__class__.__name__, ), ) diff --git a/packages/proto-plus/proto/marshal/rules/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py index 1e0695ec2f9e..5145bcf84ae4 100644 --- a/packages/proto-plus/proto/marshal/rules/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -44,7 +44,8 @@ def to_proto(self, value) -> timestamp_pb2.Timestamp: return value.timestamp_pb() if isinstance(value, datetime): return timestamp_pb2.Timestamp( - seconds=int(value.timestamp()), nanos=value.microsecond * 1000, + seconds=int(value.timestamp()), + nanos=value.microsecond * 1000, ) return value diff --git a/packages/proto-plus/proto/marshal/rules/enums.py b/packages/proto-plus/proto/marshal/rules/enums.py index d965666c59c0..9cfc312764b6 100644 --- a/packages/proto-plus/proto/marshal/rules/enums.py +++ b/packages/proto-plus/proto/marshal/rules/enums.py @@ -36,7 +36,8 @@ def to_python(self, value, *, absent: bool = None): # the user realizes that an unexpected value came along. warnings.warn( "Unrecognized {name} enum value: {value}".format( - name=self._enum.__name__, value=value, + name=self._enum.__name__, + value=value, ) ) return value diff --git a/packages/proto-plus/proto/marshal/rules/struct.py b/packages/proto-plus/proto/marshal/rules/struct.py index 27dfaf566a70..0e34587b26b1 100644 --- a/packages/proto-plus/proto/marshal/rules/struct.py +++ b/packages/proto-plus/proto/marshal/rules/struct.py @@ -47,11 +47,15 @@ def to_python(self, value, *, absent: bool = None): return str(value.string_value) if kind == "struct_value": return self._marshal.to_python( - struct_pb2.Struct, value.struct_value, absent=False, + struct_pb2.Struct, + value.struct_value, + absent=False, ) if kind == "list_value": return self._marshal.to_python( - struct_pb2.ListValue, value.list_value, absent=False, + struct_pb2.ListValue, + value.list_value, + absent=False, ) # If more variants are ever added, we want to fail loudly # instead of tacitly returning None. @@ -126,7 +130,9 @@ def to_proto(self, value) -> struct_pb2.Struct: if isinstance(value, struct_pb2.Struct): return value if isinstance(value, maps.MapComposite): - return struct_pb2.Struct(fields={k: v for k, v in value.pb.items()},) + return struct_pb2.Struct( + fields={k: v for k, v in value.pb.items()}, + ) # We got a dict (or something dict-like); convert it. answer = struct_pb2.Struct( diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 13a7635c3a7f..b157f7285a10 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -67,7 +67,9 @@ def __new__(mcls, name, bases, attrs): # Determine the name of the entry message. msg_name = "{pascal_key}Entry".format( pascal_key=re.sub( - r"_\w", lambda m: m.group()[1:].upper(), key, + r"_\w", + lambda m: m.group()[1:].upper(), + key, ).replace(key[0], key[0].upper(), 1), ) @@ -83,20 +85,26 @@ def __new__(mcls, name, bases, attrs): { "__module__": attrs.get("__module__", None), "__qualname__": "{prefix}.{name}".format( - prefix=attrs.get("__qualname__", name), name=msg_name, + prefix=attrs.get("__qualname__", name), + name=msg_name, ), "_pb_options": {"map_entry": True}, } ) entry_attrs["key"] = Field(field.map_key_type, number=1) entry_attrs["value"] = Field( - field.proto_type, number=2, enum=field.enum, message=field.message, + field.proto_type, + number=2, + enum=field.enum, + message=field.message, ) map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) # Create the repeated field for the entry message. map_fields[key] = RepeatedField( - ProtoType.MESSAGE, number=field.number, message=map_fields[msg_name], + ProtoType.MESSAGE, + number=field.number, + message=map_fields[msg_name], ) # Add the new entries to the attrs @@ -312,7 +320,13 @@ def pb(cls, obj=None, *, coerce: bool = False): if coerce: obj = cls(obj) else: - raise TypeError("%r is not an instance of %s" % (obj, cls.__name__,)) + raise TypeError( + "%r is not an instance of %s" + % ( + obj, + cls.__name__, + ) + ) return obj._pb def wrap(cls, pb): @@ -478,7 +492,11 @@ class Message(metaclass=MessageMeta): """ def __init__( - self, mapping=None, *, ignore_unknown_fields=False, **kwargs, + self, + mapping=None, + *, + ignore_unknown_fields=False, + **kwargs, ): # We accept several things for `mapping`: # * An instance of this class. @@ -519,7 +537,10 @@ def __init__( # Sanity check: Did we get something not a map? Error if so. raise TypeError( "Invalid constructor input for %s: %r" - % (self.__class__.__name__, mapping,) + % ( + self.__class__.__name__, + mapping, + ) ) params = {} @@ -564,7 +585,7 @@ def __init__( super().__setattr__("_pb", self._meta.pb(**params)) def _get_pb_type_from_key(self, key): - """Given a key, return the corresponding pb_type. + """Given a key, return the corresponding pb_type. Args: key(str): The name of the field. diff --git a/packages/proto-plus/proto/modules.py b/packages/proto-plus/proto/modules.py index 9d2e519e0563..45864a937c58 100644 --- a/packages/proto-plus/proto/modules.py +++ b/packages/proto-plus/proto/modules.py @@ -17,7 +17,8 @@ _ProtoModule = collections.namedtuple( - "ProtoModule", ["package", "marshal", "manifest"], + "ProtoModule", + ["package", "marshal", "manifest"], ) @@ -39,7 +40,11 @@ def define_module( """ if not marshal: marshal = package - return _ProtoModule(package=package, marshal=marshal, manifest=frozenset(manifest),) + return _ProtoModule( + package=package, + marshal=marshal, + manifest=frozenset(manifest), + ) __all__ = ("define_module",) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index a68e9856199c..012a7b7b32c0 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -37,7 +37,11 @@ platforms="Posix; MacOS X", include_package_data=True, install_requires=("protobuf >= 3.19.0",), - extras_require={"testing": ["google-api-core[grpc] >= 1.22.2",],}, + extras_require={ + "testing": [ + "google-api-core[grpc] >= 1.22.2", + ], + }, python_requires=">=3.6", classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/packages/proto-plus/tests/clam.py b/packages/proto-plus/tests/clam.py index a4255fe8083d..4946ebe16a91 100644 --- a/packages/proto-plus/tests/clam.py +++ b/packages/proto-plus/tests/clam.py @@ -14,7 +14,13 @@ import proto -__protobuf__ = proto.module(package="ocean.clam.v1", manifest={"Clam", "Species",},) +__protobuf__ = proto.module( + package="ocean.clam.v1", + manifest={ + "Clam", + "Species", + }, +) class Species(proto.Enum): diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index b60b91d2985a..f1f8a096d82c 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -92,7 +92,9 @@ def _register_messages(scope, iterable, sym_db): """Create and register messages from the file descriptor.""" for name, descriptor in iterable.items(): new_msg = reflection.GeneratedProtocolMessageType( - name, (message.Message,), {"DESCRIPTOR": descriptor, "__module__": None}, + name, + (message.Message,), + {"DESCRIPTOR": descriptor, "__module__": None}, ) sym_db.RegisterMessage(new_msg) setattr(scope, name, new_msg) diff --git a/packages/proto-plus/tests/enums_test.py b/packages/proto-plus/tests/enums_test.py index 6639fbf9cf7c..59c5e67116b0 100644 --- a/packages/proto-plus/tests/enums_test.py +++ b/packages/proto-plus/tests/enums_test.py @@ -14,7 +14,12 @@ import proto -__protobuf__ = proto.module(package="test.proto", manifest={"Enums",},) +__protobuf__ = proto.module( + package="test.proto", + manifest={ + "Enums", + }, +) class OneEnum(proto.Enum): diff --git a/packages/proto-plus/tests/mollusc.py b/packages/proto-plus/tests/mollusc.py index d12e4cb9b1b0..c92f161a406d 100644 --- a/packages/proto-plus/tests/mollusc.py +++ b/packages/proto-plus/tests/mollusc.py @@ -15,7 +15,12 @@ import proto import zone -__protobuf__ = proto.module(package="ocean.mollusc.v1", manifest={"Mollusc",},) +__protobuf__ = proto.module( + package="ocean.mollusc.v1", + manifest={ + "Mollusc", + }, +) class Mollusc(proto.Message): diff --git a/packages/proto-plus/tests/test_fields_int.py b/packages/proto-plus/tests/test_fields_int.py index 40f5aa38eac3..81e15e4e73ca 100644 --- a/packages/proto-plus/tests/test_fields_int.py +++ b/packages/proto-plus/tests/test_fields_int.py @@ -65,12 +65,12 @@ class Foo(proto.Message): big = proto.Field(proto.INT64, number=2) foo = Foo() - foo.big = 2 ** 40 - assert foo.big == 2 ** 40 + foo.big = 2**40 + assert foo.big == 2**40 with pytest.raises(ValueError): - foo.small = 2 ** 40 + foo.small = 2**40 with pytest.raises(ValueError): - Foo(small=2 ** 40) + Foo(small=2**40) def test_int_unsigned(): diff --git a/packages/proto-plus/tests/test_fields_map_composite.py b/packages/proto-plus/tests/test_fields_map_composite.py index 13bf0c5f6bf2..3b2d8fd9acd6 100644 --- a/packages/proto-plus/tests/test_fields_map_composite.py +++ b/packages/proto-plus/tests/test_fields_map_composite.py @@ -22,7 +22,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz(foos={"i": Foo(bar=42), "j": Foo(bar=24)}) assert len(baz.foos) == 2 @@ -36,7 +41,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz(foos={"i": {"bar": 42}, "j": {"bar": 24}}) assert len(baz.foos) == 2 @@ -52,7 +62,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz() baz.foos["i"] = Foo(bar=42) @@ -68,7 +83,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz() baz.foos["i"] = Foo() @@ -82,7 +102,12 @@ class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) class Baz(proto.Message): - foos = proto.MapField(proto.STRING, proto.MESSAGE, number=1, message=Foo,) + foos = proto.MapField( + proto.STRING, + proto.MESSAGE, + number=1, + message=Foo, + ) baz = Baz() baz.foos["i"] = Foo(bar=42) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index 2ce691efdd47..29c7e9fe49bd 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -75,7 +75,9 @@ class Baz(proto.Message): def test_repeated_composite_marshaled(): class Foo(proto.Message): timestamps = proto.RepeatedField( - proto.MESSAGE, message=timestamp_pb2.Timestamp, number=1, + proto.MESSAGE, + message=timestamp_pb2.Timestamp, + number=1, ) foo = Foo( diff --git a/packages/proto-plus/tests/test_file_info_salting.py b/packages/proto-plus/tests/test_file_info_salting.py index c7a91d93a600..4fce9105270e 100644 --- a/packages/proto-plus/tests/test_file_info_salting.py +++ b/packages/proto-plus/tests/test_file_info_salting.py @@ -34,7 +34,9 @@ def sample_file_info(name): filename, _file_info._FileInfo( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", + name=filename, + package=package, + syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), diff --git a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py index 1fc5046226c4..2d8f75eb48c7 100644 --- a/packages/proto-plus/tests/test_file_info_salting_with_manifest.py +++ b/packages/proto-plus/tests/test_file_info_salting_with_manifest.py @@ -21,7 +21,10 @@ from proto import _file_info, _package_info PACKAGE = "a.test.package.salting.with.manifest" -__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) +__protobuf__ = proto.module( + package=PACKAGE, + manifest={"This", "That"}, +) class This(proto.Message): @@ -49,7 +52,9 @@ def sample_file_info(name): filename, _file_info._FileInfo( descriptor=descriptor_pb2.FileDescriptorProto( - name=filename, package=package, syntax="proto3", + name=filename, + package=package, + syntax="proto3", ), enums=collections.OrderedDict(), messages=collections.OrderedDict(), diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 631858935226..5fe09ab5c303 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -28,7 +28,9 @@ def test_timestamp_read(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=timestamp_pb2.Timestamp(seconds=1335020400)) @@ -45,7 +47,9 @@ class Foo(proto.Message): def test_timestamp_write_init(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) @@ -60,7 +64,9 @@ class Foo(proto.Message): def test_timestamp_write(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -77,7 +83,9 @@ class Foo(proto.Message): def test_timestamp_write_pb2(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -93,7 +101,9 @@ class Foo(proto.Message): def test_timestamp_rmw_nanos(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -110,7 +120,9 @@ class Foo(proto.Message): def test_timestamp_absence(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo() @@ -120,7 +132,9 @@ class Foo(proto.Message): def test_timestamp_del(): class Foo(proto.Message): event_time = proto.Field( - proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, ) foo = Foo(event_time=DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)) @@ -130,7 +144,11 @@ class Foo(proto.Message): def test_duration_read(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=duration_pb2.Duration(seconds=60, nanos=1000)) assert isinstance(foo.ttl, timedelta) @@ -142,7 +160,11 @@ class Foo(proto.Message): def test_duration_write_init(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=timedelta(days=2)) assert isinstance(foo.ttl, timedelta) @@ -155,7 +177,11 @@ class Foo(proto.Message): def test_duration_write(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo() foo.ttl = timedelta(seconds=120) @@ -167,7 +193,11 @@ class Foo(proto.Message): def test_duration_write_pb2(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo() foo.ttl = duration_pb2.Duration(seconds=120) @@ -179,7 +209,11 @@ class Foo(proto.Message): def test_duration_del(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=timedelta(seconds=900)) del foo.ttl @@ -191,7 +225,11 @@ class Foo(proto.Message): def test_duration_nanos_rmw(): class Foo(proto.Message): - ttl = proto.Field(proto.MESSAGE, number=1, message=duration_pb2.Duration,) + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) foo = Foo(ttl=timedelta(microseconds=50)) assert foo.ttl.microseconds == 50 diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py index 6cd348c37a53..02626a8dfe1e 100644 --- a/packages/proto-plus/tests/test_marshal_types_enum.py +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -66,7 +66,11 @@ class Bivalve(proto.Enum): OYSTER = 1 class MolluscContainer(proto.Message): - bivalves = proto.RepeatedField(proto.ENUM, number=1, enum=Bivalve,) + bivalves = proto.RepeatedField( + proto.ENUM, + number=1, + enum=Bivalve, + ) mc = MolluscContainer() clam = Bivalve.CLAM @@ -82,7 +86,12 @@ class Bivalve(proto.Enum): OYSTER = 1 class MolluscContainer(proto.Message): - bivalves = proto.MapField(proto.STRING, proto.ENUM, number=1, enum=Bivalve,) + bivalves = proto.MapField( + proto.STRING, + proto.ENUM, + number=1, + enum=Bivalve, + ) mc = MolluscContainer() clam = Bivalve.CLAM diff --git a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py index ffe206a696f9..e27caf990dcb 100644 --- a/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py +++ b/packages/proto-plus/tests/test_marshal_types_wrappers_bool.py @@ -20,7 +20,11 @@ def test_bool_value_init(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) assert Foo(bar=True).bar is True assert Foo(bar=False).bar is False @@ -29,7 +33,11 @@ class Foo(proto.Message): def test_bool_value_init_dict(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) assert Foo({"bar": True}).bar is True assert Foo({"bar": False}).bar is False @@ -38,7 +46,11 @@ class Foo(proto.Message): def test_bool_value_distinction_from_bool(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) baz = proto.Field(proto.BOOL, number=2) assert Foo().bar is None @@ -63,7 +75,11 @@ class Foo(proto.Message): def test_bool_value_write_bool_value(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) foo = Foo(bar=True) foo.bar = wrappers_pb2.BoolValue() @@ -72,7 +88,11 @@ class Foo(proto.Message): def test_bool_value_del(): class Foo(proto.Message): - bar = proto.Field(proto.MESSAGE, message=wrappers_pb2.BoolValue, number=1,) + bar = proto.Field( + proto.MESSAGE, + message=wrappers_pb2.BoolValue, + number=1, + ) foo = Foo(bar=False) assert foo.bar is False diff --git a/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py index 32c2bc15ecaf..e67e8472b830 100644 --- a/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py +++ b/packages/proto-plus/tests/test_message_filename_with_and_without_manifest.py @@ -16,7 +16,10 @@ PACKAGE = "a.test.package.with.and.without.manifest" -__protobuf__ = proto.module(package=PACKAGE, manifest={"This", "That"},) +__protobuf__ = proto.module( + package=PACKAGE, + manifest={"This", "That"}, +) class This(proto.Message): diff --git a/packages/proto-plus/tests/test_message_filename_with_manifest.py b/packages/proto-plus/tests/test_message_filename_with_manifest.py index e12e5a511b6f..aa96028ef792 100644 --- a/packages/proto-plus/tests/test_message_filename_with_manifest.py +++ b/packages/proto-plus/tests/test_message_filename_with_manifest.py @@ -15,7 +15,10 @@ import proto PACKAGE = "a.test.package.with.manifest" -__protobuf__ = proto.module(package=PACKAGE, manifest={"ThisFoo", "ThisBar"},) +__protobuf__ = proto.module( + package=PACKAGE, + manifest={"ThisFoo", "ThisBar"}, +) class ThisFoo(proto.Message): diff --git a/packages/proto-plus/tests/test_message_nested.py b/packages/proto-plus/tests/test_message_nested.py index 89dfad68610d..41af9507f22e 100644 --- a/packages/proto-plus/tests/test_message_nested.py +++ b/packages/proto-plus/tests/test_message_nested.py @@ -56,7 +56,8 @@ class Bacon(proto.Message): baz = proto.Field(proto.MESSAGE, number=2, message=Baz) foo = Foo( - bar={"spam": "xyz", "eggs": False}, baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), + bar={"spam": "xyz", "eggs": False}, + baz=Foo.Baz(bacon=Foo.Baz.Bacon(value=42)), ) assert foo.bar.spam == "xyz" assert not foo.bar.eggs diff --git a/packages/proto-plus/tests/test_modules.py b/packages/proto-plus/tests/test_modules.py index 897ce46cf075..79a99da1fb00 100644 --- a/packages/proto-plus/tests/test_modules.py +++ b/packages/proto-plus/tests/test_modules.py @@ -38,7 +38,8 @@ class Foo(proto.Message): def test_module_package_explicit_marshal(): sys.modules[__name__].__protobuf__ = proto.module( - package="spam.eggs.v1", marshal="foo", + package="spam.eggs.v1", + marshal="foo", ) try: @@ -54,7 +55,10 @@ class Foo(proto.Message): def test_module_manifest(): - __protobuf__ = proto.module(manifest={"Foo", "Bar", "Baz"}, package="spam.eggs.v1",) + __protobuf__ = proto.module( + manifest={"Foo", "Bar", "Baz"}, + package="spam.eggs.v1", + ) # We want to fake a module, but modules have attribute access, and # `frame.f_locals` is a dictionary. Since we only actually care about diff --git a/packages/proto-plus/tests/zone.py b/packages/proto-plus/tests/zone.py index 4e7ef4023bb4..90bea6a878c0 100644 --- a/packages/proto-plus/tests/zone.py +++ b/packages/proto-plus/tests/zone.py @@ -16,7 +16,12 @@ import proto -__protobuf__ = proto.module(package="ocean.zone.v1", manifest={"Zone",},) +__protobuf__ = proto.module( + package="ocean.zone.v1", + manifest={ + "Zone", + }, +) class Zone(proto.Enum): From 1a086daeb7af0a7f1d93723b9cfe34df40c5e095 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 2 May 2022 05:30:06 -0400 Subject: [PATCH 175/272] ci: refactor coverage check (#312) * ci: refactor coverage check * update required checks --- packages/proto-plus/.coveragerc | 1 - .../.github/sync-repo-settings.yaml | 6 ++- .../proto-plus/.github/workflows/tests.yml | 42 +++++++++++++++---- packages/proto-plus/README.rst | 4 +- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/packages/proto-plus/.coveragerc b/packages/proto-plus/.coveragerc index 55fda8f09cab..12a43d1c07bb 100644 --- a/packages/proto-plus/.coveragerc +++ b/packages/proto-plus/.coveragerc @@ -2,7 +2,6 @@ branch = True [report] -fail_under = 100 show_missing = True omit = proto/marshal/compat.py diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index 7e74b00b12d5..dc6350a863cc 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -12,8 +12,12 @@ branchProtectionRules: - 'unit (3.7)' - 'unit (3.7, cpp)' - 'unit (3.8)' - # - 'unit (3.9, cpp)' # Don't have binary wheels for 3.9 cpp protobuf yet + - 'unit (3.9, cpp)' - 'unit (3.9)' + - 'unit (3.10, cpp)' + - 'unit (3.10)' + - cover + - OwlBot Post Processor - 'cla/google' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 026e15c24c29..da36ebaf193a 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -58,12 +58,40 @@ jobs: uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - - name: Install nox and codecov. + - name: Install nox run: | pip install nox - pip install codecov - - name: Run unit tests. - run: nox -s unit${{ matrix.variant }}-${{ matrix.python }} - - name: Submit coverage data to codecov. - run: codecov - if: always() + - name: Run unit tests + env: + COVERAGE_FILE: .coverage-${{ matrix.variant }}-${{ matrix.python }} + run: | + nox -s unit${{ matrix.variant }}-${{ matrix.python }} + - name: Upload coverage results + uses: actions/upload-artifact@v3 + with: + name: coverage-artifacts + path: .coverage-${{ matrix.variant }}-${{ matrix.python }} + cover: + runs-on: ubuntu-latest + needs: + - unit + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install coverage + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install coverage + - name: Download coverage results + uses: actions/download-artifact@v3 + with: + name: coverage-artifacts + path: .coverage-results/ + - name: Report coverage results + run: | + coverage combine .coverage-results/.coverage* + coverage report --show-missing --fail-under=100 diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 08ec2bd9f4f0..871fdebb63e5 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -1,7 +1,7 @@ Proto Plus for Python ===================== -|pypi| |release level| |docs| |codecov| +|pypi| |release level| |docs| Beautiful, Pythonic protocol buffers. @@ -26,5 +26,3 @@ Documentation :target: https://cloud.google.com/terms/launch-stages .. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest :target: https://proto-plus-python.readthedocs.io/en/latest/ -.. |codecov| image:: https://codecov.io/gh/googleapis/proto-plus-python/graph/badge.svg - :target: https://codecov.io/gh/googleapis/proto-plus-python From 133a33250beeaf33e1807db1591e5598c47bb018 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 2 May 2022 06:00:02 -0400 Subject: [PATCH 176/272] fix: default proto package name is the module name, not "" (#309) * fix: default proto package name is the module name, not "" * fix tests * lint --- packages/proto-plus/proto/_package_info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/proto/_package_info.py b/packages/proto-plus/proto/_package_info.py index d01dd79bc780..c53a6790473d 100644 --- a/packages/proto-plus/proto/_package_info.py +++ b/packages/proto-plus/proto/_package_info.py @@ -33,10 +33,11 @@ def compile(name, attrs): # Pull a reference to the module where this class is being # declared. module = sys.modules.get(attrs.get("__module__")) + module_name = module.__name__ if hasattr(module, __name__) else "" proto_module = getattr(module, "__protobuf__", object()) # A package should be present; get the marshal from there. - package = getattr(proto_module, "package", "") + package = getattr(proto_module, "package", module_name) marshal = Marshal(name=getattr(proto_module, "marshal", package)) # Done; return the data. From a61a3c8b619b084f36dfbcdb4815932a979fb7ab Mon Sep 17 00:00:00 2001 From: Gabriele Date: Mon, 2 May 2022 12:04:46 +0200 Subject: [PATCH 177/272] fix: lookup attribute instead of performing a deepcopy (#226) * fix: lookup attribute instead of performing a deepcopy * address review feedback Co-authored-by: Anthonios Partheniou --- packages/proto-plus/proto/marshal/collections/repeated.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index 01b5d2fd9873..29bacff6d6c3 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -104,7 +104,13 @@ def _pb_type(self): if len(self.pb) > 0: return type(self.pb[0]) - # We have no members in the list. + # We have no members in the list, so we get the type from the attributes. + if hasattr(self.pb, "_message_descriptor") and hasattr( + self.pb._message_descriptor, "_concrete_class" + ): + return self.pb._message_descriptor._concrete_class + + # Fallback logic in case attributes are not available # In order to get the type, we create a throw-away copy and add a # blank member to it. canary = copy.deepcopy(self.pb).add() From 88fe63938a816a101b566133c780cfba227993f0 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 19 May 2022 08:44:31 -0400 Subject: [PATCH 178/272] chore(main): release 1.20.4 (#313) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 8 ++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 28b1d33ea625..552a39a03bba 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### [1.20.4](https://github.com/googleapis/proto-plus-python/compare/v1.20.3...v1.20.4) (2022-05-02) + + +### Bug Fixes + +* default proto package name is the module name, not "" ([#309](https://github.com/googleapis/proto-plus-python/issues/309)) ([3148a1c](https://github.com/googleapis/proto-plus-python/commit/3148a1c287eb69b397c940119cd44e5067357e17)) +* lookup attribute instead of performing a deepcopy ([#226](https://github.com/googleapis/proto-plus-python/issues/226)) ([e469059](https://github.com/googleapis/proto-plus-python/commit/e469059d70bab4c2b0a38dd52c4451b3c61bf470)) + ### [1.20.3](https://github.com/googleapis/proto-plus-python/compare/v1.20.2...v1.20.3) (2022-02-18) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 012a7b7b32c0..56d9df608986 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.20.3" +version = "1.20.4" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 060c4dadef0f40c7c93136c420ff26235cced4d8 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 26 May 2022 15:40:17 -0400 Subject: [PATCH 179/272] fix(deps): require protobuf>= 3.15.0, <4.0.0dev (#315) fix(deps): require google-api-core[grpc] >= 1.31.5 --- packages/proto-plus/setup.py | 4 ++-- packages/proto-plus/testing/constraints-3.6.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 56d9df608986..736e13d9e649 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -36,10 +36,10 @@ long_description=README, platforms="Posix; MacOS X", include_package_data=True, - install_requires=("protobuf >= 3.19.0",), + install_requires=("protobuf >= 3.19.0, <4.0.0dev",), extras_require={ "testing": [ - "google-api-core[grpc] >= 1.22.2", + "google-api-core[grpc] >= 1.31.5", ], }, python_requires=">=3.6", diff --git a/packages/proto-plus/testing/constraints-3.6.txt b/packages/proto-plus/testing/constraints-3.6.txt index 852feeef0753..849c8e7cb244 100644 --- a/packages/proto-plus/testing/constraints-3.6.txt +++ b/packages/proto-plus/testing/constraints-3.6.txt @@ -5,5 +5,5 @@ # # 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.22.2 +google-api-core==1.31.5 protobuf==3.19.0 From d566c8d9a0540940cfc736ffe8011ae0526885a9 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 26 May 2022 15:48:44 -0400 Subject: [PATCH 180/272] chore(main): release 1.20.5 (#316) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 8 ++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 552a39a03bba..ddff626a4bab 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### [1.20.5](https://github.com/googleapis/proto-plus-python/compare/v1.20.4...v1.20.5) (2022-05-26) + + +### Bug Fixes + +* **deps:** require google-api-core[grpc] >= 1.31.5 ([1d13c41](https://github.com/googleapis/proto-plus-python/commit/1d13c415df457a87153a6fca202003fa83e56093)) +* **deps:** require protobuf>= 3.15.0, <4.0.0dev ([#315](https://github.com/googleapis/proto-plus-python/issues/315)) ([1d13c41](https://github.com/googleapis/proto-plus-python/commit/1d13c415df457a87153a6fca202003fa83e56093)) + ### [1.20.4](https://github.com/googleapis/proto-plus-python/compare/v1.20.3...v1.20.4) (2022-05-02) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 736e13d9e649..df96b5cbe6bc 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.20.4" +version = "1.20.5" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From e89e301a5df9dbeae646ba28c1d951f172a8b6db Mon Sep 17 00:00:00 2001 From: Dan Lee <71398022+dandhlee@users.noreply.github.com> Date: Thu, 2 Jun 2022 20:47:33 -0400 Subject: [PATCH 181/272] docs: fix changelog header to consistent size (#319) --- packages/proto-plus/CHANGELOG.md | 62 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index ddff626a4bab..f3bd3345dbd8 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -### [1.20.5](https://github.com/googleapis/proto-plus-python/compare/v1.20.4...v1.20.5) (2022-05-26) +## [1.20.5](https://github.com/googleapis/proto-plus-python/compare/v1.20.4...v1.20.5) (2022-05-26) ### Bug Fixes @@ -8,7 +8,7 @@ * **deps:** require google-api-core[grpc] >= 1.31.5 ([1d13c41](https://github.com/googleapis/proto-plus-python/commit/1d13c415df457a87153a6fca202003fa83e56093)) * **deps:** require protobuf>= 3.15.0, <4.0.0dev ([#315](https://github.com/googleapis/proto-plus-python/issues/315)) ([1d13c41](https://github.com/googleapis/proto-plus-python/commit/1d13c415df457a87153a6fca202003fa83e56093)) -### [1.20.4](https://github.com/googleapis/proto-plus-python/compare/v1.20.3...v1.20.4) (2022-05-02) +## [1.20.4](https://github.com/googleapis/proto-plus-python/compare/v1.20.3...v1.20.4) (2022-05-02) ### Bug Fixes @@ -16,21 +16,21 @@ * default proto package name is the module name, not "" ([#309](https://github.com/googleapis/proto-plus-python/issues/309)) ([3148a1c](https://github.com/googleapis/proto-plus-python/commit/3148a1c287eb69b397c940119cd44e5067357e17)) * lookup attribute instead of performing a deepcopy ([#226](https://github.com/googleapis/proto-plus-python/issues/226)) ([e469059](https://github.com/googleapis/proto-plus-python/commit/e469059d70bab4c2b0a38dd52c4451b3c61bf470)) -### [1.20.3](https://github.com/googleapis/proto-plus-python/compare/v1.20.2...v1.20.3) (2022-02-18) +## [1.20.3](https://github.com/googleapis/proto-plus-python/compare/v1.20.2...v1.20.3) (2022-02-18) ### Bug Fixes * additional logic to mitigate collisions with reserved terms ([#301](https://github.com/googleapis/proto-plus-python/issues/301)) ([c9a77df](https://github.com/googleapis/proto-plus-python/commit/c9a77df58e93a87952470d809538a08103644364)) -### [1.20.2](https://github.com/googleapis/proto-plus-python/compare/v1.20.1...v1.20.2) (2022-02-17) +## [1.20.2](https://github.com/googleapis/proto-plus-python/compare/v1.20.1...v1.20.2) (2022-02-17) ### Bug Fixes * dir(proto.Message) does not raise ([#302](https://github.com/googleapis/proto-plus-python/issues/302)) ([80dcce9](https://github.com/googleapis/proto-plus-python/commit/80dcce9099e630a6217792b6b3a14213add690e6)) -### [1.20.1](https://github.com/googleapis/proto-plus-python/compare/v1.20.0...v1.20.1) (2022-02-14) +## [1.20.1](https://github.com/googleapis/proto-plus-python/compare/v1.20.0...v1.20.1) (2022-02-14) ### Bug Fixes @@ -49,63 +49,63 @@ * workaround for buggy pytest ([#291](https://github.com/googleapis/proto-plus-python/issues/291)) ([28aa3b2](https://github.com/googleapis/proto-plus-python/commit/28aa3b2b325d2ba262f35cfc8d20e1f5fbdcf883)) -### [1.19.9](https://github.com/googleapis/proto-plus-python/compare/v1.19.8...v1.19.9) (2022-01-25) +## [1.19.9](https://github.com/googleapis/proto-plus-python/compare/v1.19.8...v1.19.9) (2022-01-25) ### Bug Fixes * add pickling support to proto messages ([#280](https://github.com/googleapis/proto-plus-python/issues/280)) ([2b7be35](https://github.com/googleapis/proto-plus-python/commit/2b7be3563f9fc2a4649a5e14d7653b85020c566f)) -### [1.19.8](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.7...v1.19.8) (2021-11-09) +## [1.19.8](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.7...v1.19.8) (2021-11-09) ### Documentation * fix typos ([#277](https://www.github.com/googleapis/proto-plus-python/issues/277)) ([e3b71e8](https://www.github.com/googleapis/proto-plus-python/commit/e3b71e8b2a81a5abb5af666c9625facb1814a609)) -### [1.19.7](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.6...v1.19.7) (2021-10-27) +## [1.19.7](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.6...v1.19.7) (2021-10-27) ### Bug Fixes * restore allowing None as value for stringy ints ([#272](https://www.github.com/googleapis/proto-plus-python/issues/272)) ([a8991d7](https://www.github.com/googleapis/proto-plus-python/commit/a8991d71ff455093fbfef142f9140d3f2928195f)) -### [1.19.6](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.5...v1.19.6) (2021-10-25) +## [1.19.6](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.5...v1.19.6) (2021-10-25) ### Bug Fixes * setting 64bit fields from strings supported ([#267](https://www.github.com/googleapis/proto-plus-python/issues/267)) ([ea7b911](https://www.github.com/googleapis/proto-plus-python/commit/ea7b91100114f5c3d40d41320b045568ac9a68f9)) -### [1.19.5](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.4...v1.19.5) (2021-10-11) +## [1.19.5](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.4...v1.19.5) (2021-10-11) ### Documentation * Clarify semantics of multiple oneof variants passed to msg ctor ([#263](https://www.github.com/googleapis/proto-plus-python/issues/263)) ([6f8a5b2](https://www.github.com/googleapis/proto-plus-python/commit/6f8a5b2098e4f6748945c53bda3d5821e62e5a0a)) -### [1.19.4](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.3...v1.19.4) (2021-10-08) +## [1.19.4](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.3...v1.19.4) (2021-10-08) ### Documentation * clarify that proto plus messages are not pickleable ([#260](https://www.github.com/googleapis/proto-plus-python/issues/260)) ([6e691dc](https://www.github.com/googleapis/proto-plus-python/commit/6e691dc27b1e540ef0661597fd89ece8f0155c97)) -### [1.19.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.2...v1.19.3) (2021-10-07) +## [1.19.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.2...v1.19.3) (2021-10-07) ### Bug Fixes * setting bytes field from python string base64 decodes before assignment ([#255](https://www.github.com/googleapis/proto-plus-python/issues/255)) ([b6f3eb6](https://www.github.com/googleapis/proto-plus-python/commit/b6f3eb6575484748126170997b8c98512763ea66)) -### [1.19.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.1...v1.19.2) (2021-09-29) +## [1.19.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.1...v1.19.2) (2021-09-29) ### Bug Fixes * ensure enums are hashable ([#252](https://www.github.com/googleapis/proto-plus-python/issues/252)) ([232341b](https://www.github.com/googleapis/proto-plus-python/commit/232341b4f4902fba1b3597bb1e1618b8f320374b)) -### [1.19.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.0...v1.19.1) (2021-09-29) +## [1.19.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.19.0...v1.19.1) (2021-09-29) ### Bug Fixes @@ -119,7 +119,7 @@ * pass 'including_default_value_fields' through to 'Message.to_dict' method ([#232](https://www.github.com/googleapis/proto-plus-python/issues/232)) ([15c2f47](https://www.github.com/googleapis/proto-plus-python/commit/15c2f479f81f0f80d451ca9b043e42cecfe7184e)) -### [1.18.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.18.0...v1.18.1) (2021-03-19) +## [1.18.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.18.0...v1.18.1) (2021-03-19) ### Bug Fixes @@ -154,21 +154,21 @@ * allow_alias for enums ([#207](https://www.github.com/googleapis/proto-plus-python/issues/207)) ([6d4d713](https://www.github.com/googleapis/proto-plus-python/commit/6d4d71399f494b9f3bd47b6f3ef0b6d3c0c547b5)) -### [1.14.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.2...v1.14.3) (2021-03-04) +## [1.14.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.2...v1.14.3) (2021-03-04) ### Bug Fixes * adding enums to a repeated field does not raise a TypeError ([#202](https://www.github.com/googleapis/proto-plus-python/issues/202)) ([2a10bbe](https://www.github.com/googleapis/proto-plus-python/commit/2a10bbecaf8955c7bf1956086aef42630112788b)) -### [1.14.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.1...v1.14.2) (2021-02-26) +## [1.14.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.1...v1.14.2) (2021-02-26) ### Bug Fixes * use the correct environment for uploading to pypi ([#199](https://www.github.com/googleapis/proto-plus-python/issues/199)) ([babdc5c](https://www.github.com/googleapis/proto-plus-python/commit/babdc5cddf08235cac3cda66200babab44204688)) -### [1.14.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.0...v1.14.1) (2021-02-26) +## [1.14.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.14.0...v1.14.1) (2021-02-26) ### Bug Fixes @@ -188,7 +188,7 @@ * proper __setitem__ and insert for RepeatedComposite ([#178](https://www.github.com/googleapis/proto-plus-python/issues/178)) ([1157a76](https://www.github.com/googleapis/proto-plus-python/commit/1157a76bb608d72389f46dc4d8e9aa00cc14ccc6)) * proper native marshal for repeated enumeration fields ([#180](https://www.github.com/googleapis/proto-plus-python/issues/180)) ([30265d6](https://www.github.com/googleapis/proto-plus-python/commit/30265d654d7f3589cbd0994d2ac564db1fd44265)) -### [1.13.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.13.0...v1.13.1) (2021-02-09) +## [1.13.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.13.0...v1.13.1) (2021-02-09) ### Bug Fixes @@ -216,14 +216,14 @@ * provide a to_dict method ([#154](https://www.github.com/googleapis/proto-plus-python/issues/154)) ([ccf903e](https://www.github.com/googleapis/proto-plus-python/commit/ccf903e3cddfcb1ff539e853594b4342914b7d61)), closes [#153](https://www.github.com/googleapis/proto-plus-python/issues/153) [#151](https://www.github.com/googleapis/proto-plus-python/issues/151) -### [1.10.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.1...v1.10.2) (2020-10-14) +## [1.10.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.1...v1.10.2) (2020-10-14) ### Documentation * explain how to use repeated struct.Value ([#148](https://www.github.com/googleapis/proto-plus-python/issues/148)) ([9634ea8](https://www.github.com/googleapis/proto-plus-python/commit/9634ea8fa464c0d34f13469016f23cc2e986d973)), closes [#104](https://www.github.com/googleapis/proto-plus-python/issues/104) -### [1.10.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.0...v1.10.1) (2020-10-08) +## [1.10.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.0...v1.10.1) (2020-10-08) ### Bug Fixes @@ -249,7 +249,7 @@ * third party enums don't break first class enums ([#118](https://www.github.com/googleapis/proto-plus-python/issues/118)) ([50b87af](https://www.github.com/googleapis/proto-plus-python/commit/50b87af481bb1f19d10d64e88dc9ee39c2d5b6f8)), closes [#103](https://www.github.com/googleapis/proto-plus-python/issues/103) -### [1.9.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.0...v1.9.1) (2020-09-08) +## [1.9.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.0...v1.9.1) (2020-09-08) ### Reverts @@ -268,7 +268,7 @@ * json serialization and deserialization support stringy enums ([#112](https://www.github.com/googleapis/proto-plus-python/issues/112)) ([8d2e3a3](https://www.github.com/googleapis/proto-plus-python/commit/8d2e3a3439650dab9ca7c6ff49ed067838a02a45)), closes [#107](https://www.github.com/googleapis/proto-plus-python/issues/107) -### [1.8.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.8.0...v1.8.1) (2020-08-28) +## [1.8.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.8.0...v1.8.1) (2020-08-28) ### Bug Fixes @@ -282,7 +282,7 @@ * allow enum strings in json serialization and deserialization ([#107](https://www.github.com/googleapis/proto-plus-python/issues/107)) ([a082f85](https://www.github.com/googleapis/proto-plus-python/commit/a082f85ffcb72e2c53c0e33e40e6df2927a41259)) -### [1.7.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.7.0...v1.7.1) (2020-08-17) +## [1.7.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.7.0...v1.7.1) (2020-08-17) ### Bug Fixes @@ -303,21 +303,21 @@ * more performance optimizations ([#92](https://www.github.com/googleapis/proto-plus-python/issues/92)) ([19b1519](https://www.github.com/googleapis/proto-plus-python/commit/19b151960de7c83ac82e670b06cb47d6e885f627)) -### [1.5.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.2...v1.5.3) (2020-08-04) +## [1.5.3](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.2...v1.5.3) (2020-08-04) ### Bug Fixes * yet more perf tweaks ([#90](https://www.github.com/googleapis/proto-plus-python/issues/90)) ([eb7891c](https://www.github.com/googleapis/proto-plus-python/commit/eb7891cf05124803352b2f4fd719937356bf9167)) -### [1.5.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.1...v1.5.2) (2020-08-03) +## [1.5.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.1...v1.5.2) (2020-08-03) ### Bug Fixes * tweak to_python ([#88](https://www.github.com/googleapis/proto-plus-python/issues/88)) ([5459ede](https://www.github.com/googleapis/proto-plus-python/commit/5459ede75597b06df5a211b0e317fb2c1f4b034e)) -### [1.5.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.0...v1.5.1) (2020-07-30) +## [1.5.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.5.0...v1.5.1) (2020-07-30) ### Bug Fixes @@ -331,14 +331,14 @@ * support fixed filename salt to allow proto-plus use with schema registry tools ([#61](https://www.github.com/googleapis/proto-plus-python/issues/61)) ([ea86eb9](https://www.github.com/googleapis/proto-plus-python/commit/ea86eb9ac694ed1f0e711698429449f41ecfedfc)) -### [1.4.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.1...v1.4.2) (2020-07-23) +## [1.4.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.1...v1.4.2) (2020-07-23) ### Bug Fixes * getattr on an invalid field raises AttributeError ([#73](https://www.github.com/googleapis/proto-plus-python/issues/73)) ([74ea8f0](https://www.github.com/googleapis/proto-plus-python/commit/74ea8f0cd9083939e53d1de2450b649500281b9a)), closes [#31](https://www.github.com/googleapis/proto-plus-python/issues/31) -### [1.4.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.0...v1.4.1) (2020-07-23) +## [1.4.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.4.0...v1.4.1) (2020-07-23) ### Bug Fixes @@ -352,7 +352,7 @@ * prevent unnecessary copies when deserializing proto ([#63](https://www.github.com/googleapis/proto-plus-python/issues/63)) ([5e1c061](https://www.github.com/googleapis/proto-plus-python/commit/5e1c0619b5f4c5d2a6a75ae6d45a53fef2e58823)) -### [1.3.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.1...v1.3.2) (2020-07-22) +## [1.3.2](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.1...v1.3.2) (2020-07-22) ### Bug Fixes @@ -360,7 +360,7 @@ * correctly handle passed in vanilla datetime.datetime ([#57](https://www.github.com/googleapis/proto-plus-python/issues/57)) ([a770816](https://www.github.com/googleapis/proto-plus-python/commit/a770816197cbce60ee023bd5b6ee6bd2d970ded8)), closes [googleapis/gapic-generator-python#544](https://www.github.com/googleapis/gapic-generator-python/issues/544) * update encrypted pypi passwd ([#58](https://www.github.com/googleapis/proto-plus-python/issues/58)) ([d985233](https://www.github.com/googleapis/proto-plus-python/commit/d9852336d83717cb9ff24b6bec3ef1463239fea1)) -### [1.3.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.0...v1.3.1) (2020-07-21) +## [1.3.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.3.0...v1.3.1) (2020-07-21) ### Bug Fixes From 95e5c84806106b1428a43356ea695926a5d749b0 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Fri, 3 Jun 2022 14:36:27 -0400 Subject: [PATCH 182/272] chore: test minimum dependencies in python 3.7 (#321) --- packages/proto-plus/testing/constraints-3.7.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/proto-plus/testing/constraints-3.7.txt b/packages/proto-plus/testing/constraints-3.7.txt index e69de29bb2d1..849c8e7cb244 100644 --- a/packages/proto-plus/testing/constraints-3.7.txt +++ b/packages/proto-plus/testing/constraints-3.7.txt @@ -0,0 +1,9 @@ +# 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 From 2c83e3857ab8a610e8d5358ea32b11442b4fd131 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 13 Jun 2022 14:56:45 -0400 Subject: [PATCH 183/272] fix(deps): allow protobuf < 5.0.0 (#324) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index df96b5cbe6bc..f34262cabc24 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -36,7 +36,7 @@ long_description=README, platforms="Posix; MacOS X", include_package_data=True, - install_requires=("protobuf >= 3.19.0, <4.0.0dev",), + install_requires=("protobuf >= 3.19.0, <5.0.0dev",), extras_require={ "testing": [ "google-api-core[grpc] >= 1.31.5", From 37e19886c61532f181d74ff8ef922f1a04674653 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 14 Jun 2022 00:12:27 +0200 Subject: [PATCH 184/272] chore(deps): update actions/setup-python action to v4 (#323) Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/pypi-upload.yaml | 2 +- packages/proto-plus/.github/workflows/tests.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/workflows/pypi-upload.yaml b/packages/proto-plus/.github/workflows/pypi-upload.yaml index b4151b687a73..edd80335260a 100644 --- a/packages/proto-plus/.github/workflows/pypi-upload.yaml +++ b/packages/proto-plus/.github/workflows/pypi-upload.yaml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index da36ebaf193a..313b1b372e50 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install black @@ -35,7 +35,7 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install nox. @@ -55,7 +55,7 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install nox @@ -79,7 +79,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install coverage From a6783529eff625a5ed7a8e9379827b90f4e1e9ea Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:03:55 -0400 Subject: [PATCH 185/272] chore(main): release 1.20.6 (#320) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Anthonios Partheniou --- packages/proto-plus/CHANGELOG.md | 12 ++++++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index f3bd3345dbd8..901e01627fc1 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [1.20.6](https://github.com/googleapis/proto-plus-python/compare/v1.20.5...v1.20.6) (2022-06-13) + + +### Bug Fixes + +* **deps:** allow protobuf < 5.0.0 ([#324](https://github.com/googleapis/proto-plus-python/issues/324)) ([af4f56e](https://github.com/googleapis/proto-plus-python/commit/af4f56edb0bfbfa808f7def37e19b24bd27d4b40)) + + +### Documentation + +* fix changelog header to consistent size ([#319](https://github.com/googleapis/proto-plus-python/issues/319)) ([27d2003](https://github.com/googleapis/proto-plus-python/commit/27d2003571342e24aa74d29a45fa49d2328ff76d)) + ## [1.20.5](https://github.com/googleapis/proto-plus-python/compare/v1.20.4...v1.20.5) (2022-05-26) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index f34262cabc24..e427e582dcff 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.20.5" +version = "1.20.6" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 07cced4a02f7200a19ac567d2ca5093a45b4dd1d Mon Sep 17 00:00:00 2001 From: Aza Tulepbergenov Date: Fri, 1 Jul 2022 16:52:36 -0700 Subject: [PATCH 186/272] feat: add initial files to allow protobuf 4.20 (#327) * feat: add initial files to allow protobuf 4.20 This PR integrates protobuf 4.xx into codebase. Co-authored-by: Anthonios Partheniou * chore: address pr comment. Co-authored-by: Anthonios Partheniou --- .../proto-plus/.github/workflows/tests.yml | 2 +- packages/proto-plus/noxfile.py | 10 ++++++-- packages/proto-plus/proto/_package_info.py | 8 ++++++- .../proto/marshal/collections/maps.py | 13 +++++++--- packages/proto-plus/proto/marshal/compat.py | 11 ++++++++- packages/proto-plus/proto/marshal/marshal.py | 2 +- packages/proto-plus/proto/message.py | 24 +++++++++++++++---- packages/proto-plus/proto/utils.py | 10 ++++++++ packages/proto-plus/tests/conftest.py | 9 +++++-- .../tests/test_fields_repeated_composite.py | 1 + .../proto-plus/tests/test_message_filename.py | 5 +++- 11 files changed, 78 insertions(+), 17 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 313b1b372e50..eff0c8ff378a 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: strategy: matrix: python: ['3.6', '3.7', '3.8', '3.9', '3.10'] - variant: ['', cpp] + variant: ['', 'cpp', 'upb'] steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@0.9.1 diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 0e737e7373ad..0e65692ee91d 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -42,7 +42,8 @@ def unit(session, proto="python"): session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto 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") session.run( "py.test", "-W=error", @@ -54,7 +55,7 @@ def unit(session, proto="python"): "--cov-config=.coveragerc", "--cov-report=term", "--cov-report=html", - os.path.join("tests", ""), + "tests", ] ), ) @@ -68,6 +69,11 @@ def unitcpp(session): return unit(session, proto="cpp") +@nox.session(python=PYTHON_VERSIONS) +def unitupb(session): + return unit(session, proto="upb") + + # Just use the most recent version for docs @nox.session(python=PYTHON_VERSIONS[-1]) def docs(session): diff --git a/packages/proto-plus/proto/_package_info.py b/packages/proto-plus/proto/_package_info.py index c53a6790473d..75e89ebaac18 100644 --- a/packages/proto-plus/proto/_package_info.py +++ b/packages/proto-plus/proto/_package_info.py @@ -37,7 +37,13 @@ def compile(name, attrs): proto_module = getattr(module, "__protobuf__", object()) # A package should be present; get the marshal from there. - package = getattr(proto_module, "package", module_name) + # TODO: Revert to empty string as a package value after protobuf fix. + # When package is empty, upb based protobuf fails with an + # "TypeError: Couldn't build proto file into descriptor pool: invalid name: empty part ()' means" + # during an attempt to add to descriptor pool. + package = getattr( + proto_module, "package", module_name if module_name else "_default_package" + ) marshal = Marshal(name=getattr(proto_module, "marshal", package)) # Done; return the data. diff --git a/packages/proto-plus/proto/marshal/collections/maps.py b/packages/proto-plus/proto/marshal/collections/maps.py index 8ed11349d22f..a66e8fc5cc62 100644 --- a/packages/proto-plus/proto/marshal/collections/maps.py +++ b/packages/proto-plus/proto/marshal/collections/maps.py @@ -15,6 +15,7 @@ import collections from proto.utils import cached_property +from google.protobuf.message import Message class MapComposite(collections.abc.MutableMapping): @@ -58,15 +59,21 @@ def __getitem__(self, key): def __setitem__(self, key, value): pb_value = self._marshal.to_proto(self._pb_type, value, strict=True) - # Directly setting a key is not allowed; however, protocol buffers # is so permissive that querying for the existence of a key will in # of itself create it. # # Therefore, we create a key that way (clearing any fields that may # be set) and then merge in our values. - self.pb[key].Clear() - self.pb[key].MergeFrom(pb_value) + # TODO: self.pb[key] should always be Message. Remove this after protobuf fix. + # In UPB, sometimes self.pb[key] is not always a proto. + # This happens during marshalling when the pb_value is upb.MapCompositeContainer + # so it's not marshalled correcrtly (i.e. should be scalar values not composite). + if isinstance(self.pb[key], Message): + self.pb[key].Clear() + self.pb[key].MergeFrom(pb_value) + else: + self.pb[key] = value def __delitem__(self, key): self.pb.pop(key) diff --git a/packages/proto-plus/proto/marshal/compat.py b/packages/proto-plus/proto/marshal/compat.py index c1960d5752eb..e10999bbad2c 100644 --- a/packages/proto-plus/proto/marshal/compat.py +++ b/packages/proto-plus/proto/marshal/compat.py @@ -20,14 +20,23 @@ from google.protobuf.internal import containers +# Import protobuf 4.xx first and fallback to earlier version +# if not present. try: - from google.protobuf.pyext import _message + from google._upb import _message except ImportError: _message = None +if not _message: + try: + from google.protobuf.pyext import _message + except ImportError: + _message = None + repeated_composite_types = (containers.RepeatedCompositeFieldContainer,) repeated_scalar_types = (containers.RepeatedScalarFieldContainer,) map_composite_types = (containers.MessageMap,) + if _message: repeated_composite_types += (_message.RepeatedCompositeContainer,) repeated_scalar_types += (_message.RepeatedScalarContainer,) diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index e7f8d4d92161..0a12752e4d98 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -25,6 +25,7 @@ from proto.marshal.collections import MapComposite from proto.marshal.collections import Repeated from proto.marshal.collections import RepeatedComposite + from proto.marshal.rules import bytes as pb_bytes from proto.marshal.rules import stringy_numbers from proto.marshal.rules import dates @@ -219,7 +220,6 @@ def to_proto(self, proto_type, value, *, strict: bool = False): got=pb_value.__class__.__name__, ), ) - # Return the final value. return pb_value diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index b157f7285a10..6726988a74aa 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -29,6 +29,10 @@ from proto.fields import RepeatedField from proto.marshal import Marshal from proto.primitives import ProtoType +from proto.utils import has_upb + + +_upb = has_upb() # Important to cache result here. class MessageMeta(type): @@ -568,11 +572,21 @@ def __init__( # See related issue # https://github.com/googleapis/python-api-core/issues/227 if isinstance(value, dict): - keys_to_update = [ - item - for item in value - if not hasattr(pb_type, item) and hasattr(pb_type, f"{item}_") - ] + if _upb: + # In UPB, pb_type is MessageMeta which doesn't expose attrs like it used to in Python/CPP. + keys_to_update = [ + item + for item in value + if item not in pb_type.DESCRIPTOR.fields_by_name + and f"{item}_" in pb_type.DESCRIPTOR.fields_by_name + ] + else: + keys_to_update = [ + item + for item in value + if not hasattr(pb_type, item) + and hasattr(pb_type, f"{item}_") + ] for item in keys_to_update: value[f"{item}_"] = value.pop(item) diff --git a/packages/proto-plus/proto/utils.py b/packages/proto-plus/proto/utils.py index 6367598027d2..ac3c471a2e8b 100644 --- a/packages/proto-plus/proto/utils.py +++ b/packages/proto-plus/proto/utils.py @@ -15,6 +15,16 @@ import functools +def has_upb(): + try: + from google._upb import _message # pylint: disable=unused-import + + has_upb = True + except ImportError: + has_upb = False + return has_upb + + def cached_property(fx): """Make the callable into a cached property. diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index f1f8a096d82c..765326c674ef 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/tests/conftest.py @@ -23,6 +23,7 @@ from proto._file_info import _FileInfo from proto.marshal import Marshal from proto.marshal import rules +from proto.utils import has_upb def pytest_runtest_setup(item): @@ -37,10 +38,14 @@ def pytest_runtest_setup(item): mock.patch.object(symbol_database, "Default", return_value=sym_db), ] if descriptor_pool._USE_C_DESCRIPTORS: - from google.protobuf.pyext import _message item._mocks.append( - mock.patch("google.protobuf.pyext._message.default_pool", pool) + mock.patch( + "google._upb._message.default_pool" + if has_upb() + else "google.protobuf.pyext._message.default_pool", + pool, + ) ) [i.start() for i in item._mocks] diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index 29c7e9fe49bd..db6be27baef3 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -35,6 +35,7 @@ class Baz(proto.Message): assert len(baz.foos) == 1 assert baz.foos == baz.foos assert baz.foos[0].bar == 42 + assert isinstance(baz.foos[0], Foo) def test_repeated_composite_equality(): diff --git a/packages/proto-plus/tests/test_message_filename.py b/packages/proto-plus/tests/test_message_filename.py index 3cce260a1206..dcb2ebfa0bc9 100644 --- a/packages/proto-plus/tests/test_message_filename.py +++ b/packages/proto-plus/tests/test_message_filename.py @@ -20,4 +20,7 @@ def test_filename_includes_classname_salt(): class Foo(proto.Message): bar = proto.Field(proto.INT32, number=1) - assert Foo.pb(Foo()).DESCRIPTOR.file.name == "test_message_filename_foo.proto" + assert ( + Foo.pb(Foo()).DESCRIPTOR.file.name + == "test_message_filename__default_package.foo.proto" + ) From c258dd78a6c6be5bbb15720f24c67cc6074b0964 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 15 Jul 2022 14:33:57 +0200 Subject: [PATCH 187/272] ci: migrate to Github Actions Concurrency for cancelling workflows (#328) * chore(deps): update styfle/cancel-workflow-action action to v0.10.0 * ci: migrate to Github Actions Concurrency for cancelling workflows Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/tests.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index eff0c8ff378a..880449889144 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -9,14 +9,14 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +concurrency: + group: tests-${{ github.head_ref }} + cancel-in-progress: true + jobs: style-check: runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v4 @@ -29,10 +29,6 @@ jobs: docs: runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v4 @@ -49,10 +45,6 @@ jobs: python: ['3.6', '3.7', '3.8', '3.9', '3.10'] variant: ['', 'cpp', 'upb'] steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v4 From 436ab7349322fad3600845f07158dde91744cd51 Mon Sep 17 00:00:00 2001 From: Aza Tulepbergenov Date: Thu, 4 Aug 2022 14:39:15 -0700 Subject: [PATCH 188/272] fix: fixes bug in the test. (#332) * fix: fixes bug in the test. --- packages/proto-plus/proto/marshal/collections/maps.py | 11 ++--------- packages/proto-plus/tests/test_marshal_types_enum.py | 3 +-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/proto-plus/proto/marshal/collections/maps.py b/packages/proto-plus/proto/marshal/collections/maps.py index a66e8fc5cc62..3c4857161458 100644 --- a/packages/proto-plus/proto/marshal/collections/maps.py +++ b/packages/proto-plus/proto/marshal/collections/maps.py @@ -65,15 +65,8 @@ def __setitem__(self, key, value): # # Therefore, we create a key that way (clearing any fields that may # be set) and then merge in our values. - # TODO: self.pb[key] should always be Message. Remove this after protobuf fix. - # In UPB, sometimes self.pb[key] is not always a proto. - # This happens during marshalling when the pb_value is upb.MapCompositeContainer - # so it's not marshalled correcrtly (i.e. should be scalar values not composite). - if isinstance(self.pb[key], Message): - self.pb[key].Clear() - self.pb[key].MergeFrom(pb_value) - else: - self.pb[key] = value + self.pb[key].Clear() + self.pb[key].MergeFrom(pb_value) def __delitem__(self, key): self.pb.pop(key) diff --git a/packages/proto-plus/tests/test_marshal_types_enum.py b/packages/proto-plus/tests/test_marshal_types_enum.py index 02626a8dfe1e..acb554618dfa 100644 --- a/packages/proto-plus/tests/test_marshal_types_enum.py +++ b/packages/proto-plus/tests/test_marshal_types_enum.py @@ -97,5 +97,4 @@ class MolluscContainer(proto.Message): clam = Bivalve.CLAM mc.bivalves["clam"] = clam mc.bivalves["oyster"] = 1 - - assert mc.bivalves == {"clam": clam, "oyster": Bivalve.OYSTER} + assert dict(mc.bivalves) == {"clam": clam, "oyster": Bivalve.OYSTER} From 675a1cd7913619e9d9dceae83ed9c6adb74bd6ce Mon Sep 17 00:00:00 2001 From: Vadym Matsishevskyi <25311427+vam-google@users.noreply.github.com> Date: Fri, 5 Aug 2022 13:22:28 -0700 Subject: [PATCH 189/272] fix: Fix Timestamp, Duration and FieldMask marshaling in REST transport (#334) * fix: Fix Timestamp, Duration and FieldMask marshaling in REST transport This fixes https://github.com/googleapis/proto-plus-python/issues/333 --- packages/proto-plus/proto/marshal/marshal.py | 5 + .../proto-plus/proto/marshal/rules/dates.py | 8 ++ .../proto/marshal/rules/field_mask.py | 36 +++++++ .../tests/test_marshal_field_mask.py | 100 ++++++++++++++++++ .../tests/test_marshal_types_dates.py | 34 ++++++ 5 files changed, 183 insertions(+) create mode 100644 packages/proto-plus/proto/marshal/rules/field_mask.py create mode 100644 packages/proto-plus/tests/test_marshal_field_mask.py diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index 0a12752e4d98..bd21cbc1a27e 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -18,6 +18,7 @@ from google.protobuf import message from google.protobuf import duration_pb2 from google.protobuf import timestamp_pb2 +from google.protobuf import field_mask_pb2 from google.protobuf import struct_pb2 from google.protobuf import wrappers_pb2 @@ -31,6 +32,7 @@ from proto.marshal.rules import dates from proto.marshal.rules import struct from proto.marshal.rules import wrappers +from proto.marshal.rules import field_mask from proto.primitives import ProtoType @@ -126,6 +128,9 @@ def reset(self): self.register(timestamp_pb2.Timestamp, dates.TimestampRule()) self.register(duration_pb2.Duration, dates.DurationRule()) + # Register FieldMask wrappers. + self.register(field_mask_pb2.FieldMask, field_mask.FieldMaskRule()) + # Register nullable primitive wrappers. self.register(wrappers_pb2.BoolValue, wrappers.BoolValueRule()) self.register(wrappers_pb2.BytesValue, wrappers.BytesValueRule()) diff --git a/packages/proto-plus/proto/marshal/rules/dates.py b/packages/proto-plus/proto/marshal/rules/dates.py index 5145bcf84ae4..33d12829b3cc 100644 --- a/packages/proto-plus/proto/marshal/rules/dates.py +++ b/packages/proto-plus/proto/marshal/rules/dates.py @@ -47,6 +47,10 @@ def to_proto(self, value) -> timestamp_pb2.Timestamp: seconds=int(value.timestamp()), nanos=value.microsecond * 1000, ) + if isinstance(value, str): + timestamp_value = timestamp_pb2.Timestamp() + timestamp_value.FromJsonString(value=value) + return timestamp_value return value @@ -74,4 +78,8 @@ def to_proto(self, value) -> duration_pb2.Duration: seconds=value.days * 86400 + value.seconds, nanos=value.microseconds * 1000, ) + if isinstance(value, str): + duration_value = duration_pb2.Duration() + duration_value.FromJsonString(value=value) + return duration_value return value diff --git a/packages/proto-plus/proto/marshal/rules/field_mask.py b/packages/proto-plus/proto/marshal/rules/field_mask.py new file mode 100644 index 000000000000..348e7e3995d4 --- /dev/null +++ b/packages/proto-plus/proto/marshal/rules/field_mask.py @@ -0,0 +1,36 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.protobuf import field_mask_pb2 + + +class FieldMaskRule: + """A marshal between FieldMask and strings. + + See https://github.com/googleapis/proto-plus-python/issues/333 + and + https://developers.google.com/protocol-buffers/docs/proto3#json + for more details. + """ + + def to_python(self, value, *, absent: bool = None): + return value + + def to_proto(self, value): + if isinstance(value, str): + field_mask_value = field_mask_pb2.FieldMask() + field_mask_value.FromJsonString(value=value) + return field_mask_value + + return value diff --git a/packages/proto-plus/tests/test_marshal_field_mask.py b/packages/proto-plus/tests/test_marshal_field_mask.py new file mode 100644 index 000000000000..ffb36a2ff7a6 --- /dev/null +++ b/packages/proto-plus/tests/test_marshal_field_mask.py @@ -0,0 +1,100 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.protobuf import field_mask_pb2 + +import proto +from proto.marshal.marshal import BaseMarshal + + +def test_field_mask_read(): + class Foo(proto.Message): + mask = proto.Field( + proto.MESSAGE, + number=1, + message=field_mask_pb2.FieldMask, + ) + + foo = Foo(mask=field_mask_pb2.FieldMask(paths=["f.b.d", "f.c"])) + + assert isinstance(foo.mask, field_mask_pb2.FieldMask) + assert foo.mask.paths == ["f.b.d", "f.c"] + + +def test_field_mask_write_string(): + class Foo(proto.Message): + mask = proto.Field( + proto.MESSAGE, + number=1, + message=field_mask_pb2.FieldMask, + ) + + foo = Foo() + foo.mask = "f.b.d,f.c" + + assert isinstance(foo.mask, field_mask_pb2.FieldMask) + assert foo.mask.paths == ["f.b.d", "f.c"] + + +def test_field_mask_write_pb2(): + class Foo(proto.Message): + mask = proto.Field( + proto.MESSAGE, + number=1, + message=field_mask_pb2.FieldMask, + ) + + foo = Foo() + foo.mask = field_mask_pb2.FieldMask(paths=["f.b.d", "f.c"]) + + assert isinstance(foo.mask, field_mask_pb2.FieldMask) + assert foo.mask.paths == ["f.b.d", "f.c"] + + +def test_field_mask_absence(): + class Foo(proto.Message): + mask = proto.Field( + proto.MESSAGE, + number=1, + message=field_mask_pb2.FieldMask, + ) + + foo = Foo() + assert not foo.mask.paths + + +def test_timestamp_del(): + class Foo(proto.Message): + mask = proto.Field( + proto.MESSAGE, + number=1, + message=field_mask_pb2.FieldMask, + ) + + foo = Foo() + foo.mask = field_mask_pb2.FieldMask(paths=["f.b.d", "f.c"]) + + del foo.mask + assert not foo.mask.paths + + +def test_timestamp_to_python_idempotent(): + # This path can never run in the current configuration because proto + # values are the only thing ever saved, and `to_python` is a read method. + # + # However, we test idempotency for consistency with `to_proto` and + # general resiliency. + marshal = BaseMarshal() + py_value = "f.b.d,f.c" + assert marshal.to_python(field_mask_pb2.FieldMask, py_value) is py_value diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 5fe09ab5c303..21841b990a22 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -98,6 +98,24 @@ class Foo(proto.Message): assert Foo.pb(foo).event_time.seconds == 1335020400 +def test_timestamp_write_string(): + class Foo(proto.Message): + event_time = proto.Field( + proto.MESSAGE, + number=1, + message=timestamp_pb2.Timestamp, + ) + + foo = Foo() + foo.event_time = "2012-04-21T15:00:00Z" + assert isinstance(foo.event_time, DatetimeWithNanoseconds) + assert isinstance(Foo.pb(foo).event_time, timestamp_pb2.Timestamp) + assert foo.event_time.year == 2012 + assert foo.event_time.month == 4 + assert foo.event_time.hour == 15 + assert Foo.pb(foo).event_time.seconds == 1335020400 + + def test_timestamp_rmw_nanos(): class Foo(proto.Message): event_time = proto.Field( @@ -207,6 +225,22 @@ class Foo(proto.Message): assert Foo.pb(foo).ttl.seconds == 120 +def test_duration_write_string(): + class Foo(proto.Message): + ttl = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + + foo = Foo() + foo.ttl = "120s" + assert isinstance(foo.ttl, timedelta) + assert isinstance(Foo.pb(foo).ttl, duration_pb2.Duration) + assert foo.ttl.seconds == 120 + assert Foo.pb(foo).ttl.seconds == 120 + + def test_duration_del(): class Foo(proto.Message): ttl = proto.Field( From 8f4000cb0ec34893b50538a638ed1ba3ccaea574 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 10 Aug 2022 13:20:05 -0400 Subject: [PATCH 190/272] chore(main): release 1.22.0 (#330) * chore(main): release 1.22.0 * add changelog for PR #327 * ci: fix docs build Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/tests.yml | 2 +- packages/proto-plus/CHANGELOG.md | 11 +++++++++++ packages/proto-plus/noxfile.py | 2 +- packages/proto-plus/setup.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 880449889144..219bb5a3d598 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: - name: Set up Python 3.8 uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: "3.10" - name: Install nox. run: python -m pip install nox - name: Build the documentation. diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 901e01627fc1..1e7dec80d7a8 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.22.0](https://github.com/googleapis/proto-plus-python/compare/v1.21.0...v1.22.0) (2022-08-10) + +### Features + +* Add support for protobuf v4 ([#327](https://github.com/googleapis/proto-plus-python/issues/327)) ([ed353aa](https://github.com/googleapis/proto-plus-python/commit/ed353aaf8bd5a659535eb493221320e449f3f637)) + +### Bug Fixes + +* Fix Timestamp, Duration and FieldMask marshaling in REST transport ([a2e7300](https://github.com/googleapis/proto-plus-python/commit/a2e7300368625ceec39dd2c2dfb96291ad8cf1f1)) +* fixes bug in the test. ([#332](https://github.com/googleapis/proto-plus-python/issues/332)) ([f85f470](https://github.com/googleapis/proto-plus-python/commit/f85f470c880a7bff7f3c813d33d15e657e7b5123)) + ## [1.20.6](https://github.com/googleapis/proto-plus-python/compare/v1.20.5...v1.20.6) (2022-06-13) diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 0e65692ee91d..576a616c504b 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -79,7 +79,7 @@ def unitupb(session): def docs(session): """Build the docs.""" - session.install("sphinx", "sphinx_rtd_theme") + session.install("sphinx==4.2.0", "sphinx_rtd_theme") session.install(".") # Build the docs! diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index e427e582dcff..cab688e38049 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.20.6" +version = "1.22.0" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From c9e2299c6f65094d8552d3af9d6b545570424ce9 Mon Sep 17 00:00:00 2001 From: Vadym Matsishevskyi <25311427+vam-google@users.noreply.github.com> Date: Mon, 29 Aug 2022 11:05:54 -0700 Subject: [PATCH 191/272] fix: Add no-pretty print option (#336) --- packages/proto-plus/proto/message.py | 5 +++++ packages/proto-plus/tests/test_json.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 6726988a74aa..253e6240effc 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -376,6 +376,7 @@ def to_json( use_integers_for_enums=True, including_default_value_fields=True, preserving_proto_field_name=False, + indent=2, ) -> str: """Given a message instance, serialize it to json @@ -388,6 +389,9 @@ def to_json( 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. + indent: The JSON object will be pretty-printed with this indent level. + An indent level of 0 or negative will only insert newlines. + Pass None for the most compact representation without newlines. Returns: str: The json string representation of the protocol buffer. @@ -397,6 +401,7 @@ def to_json( use_integers_for_enums=use_integers_for_enums, including_default_value_fields=including_default_value_fields, preserving_proto_field_name=preserving_proto_field_name, + indent=indent, ) def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index 8faa96d4c7db..93ca936c395c 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -28,6 +28,16 @@ class Squid(proto.Message): assert json == '{"massKg":100}' +def test_message_to_json_no_indent(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.INT32, number=1) + name = proto.Field(proto.STRING, number=2) + + s = Squid(mass_kg=100, name="no_new_lines_squid") + json = Squid.to_json(s, indent=None) + assert json == '{"massKg": 100, "name": "no_new_lines_squid"}' + + def test_message_from_json(): class Squid(proto.Message): mass_kg = proto.Field(proto.INT32, number=1) From 1ad82f919f44372cf752edcbe9fd246e1465ef2b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 30 Aug 2022 09:37:20 -0400 Subject: [PATCH 192/272] chore(main): release 1.22.1 (#337) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 1e7dec80d7a8..931d29c782c2 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.22.1](https://github.com/googleapis/proto-plus-python/compare/v1.22.0...v1.22.1) (2022-08-29) + + +### Bug Fixes + +* Add no-pretty print option ([#336](https://github.com/googleapis/proto-plus-python/issues/336)) ([1bb228a](https://github.com/googleapis/proto-plus-python/commit/1bb228ac93543d28871645a22e5ac7fb20a0a55c)) + ## [1.22.0](https://github.com/googleapis/proto-plus-python/compare/v1.21.0...v1.22.0) (2022-08-10) ### Features diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index cab688e38049..a1d51f7d6fd7 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.22.0" +version = "1.22.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 73742379bd7b405de5b3a8440f0309b497fd7ec7 Mon Sep 17 00:00:00 2001 From: "Bob \"Wombat\" Hogg" Date: Wed, 9 Nov 2022 13:01:10 -0500 Subject: [PATCH 193/272] docs: Fix typo in index.rst (#342) --- packages/proto-plus/docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/docs/index.rst b/packages/proto-plus/docs/index.rst index f12b7e7c6d9e..d94608dd5c97 100644 --- a/packages/proto-plus/docs/index.rst +++ b/packages/proto-plus/docs/index.rst @@ -4,7 +4,7 @@ Proto Plus for Python Beautiful, Pythonic protocol buffers. This library provides a clean, readable, straightforward pattern for -declaraing messages in `protocol buffers`_. It provides a wrapper around +declaring messages in `protocol buffers`_. It provides a wrapper around the official implementation, so that using messages feels natural while retaining the power and flexibility of protocol buffers. From bad805196deeba7c840e08c1a2ee142f00672318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Fri, 6 Jan 2023 00:15:56 +0100 Subject: [PATCH 194/272] fix: Add support for Python 3.11 (#329) * Adjust to enum changes in Python 3.11.0b3 There are two changes: Changes in the actual code: - _member_names changed from a list to a dict in https://github.com/python/cpython/pull/28907 - we instance-check and remove by list-specific or dict-specific way Change in the tests only: - accessing other enum members via instance attributes is no longer possible - we access them via the class instead - we leave the original test in a try-except block Some of the Python enum changes might get reverted, see https://github.com/python/cpython/issues/93910 But the fix is backwards compatible. Fixes https://github.com/googleapis/proto-plus-python/issues/326 * ci: unit test session with python 3.11.0-beta.3 * ci: add python v3.11.0-beta.3 to noxfile.py * another attempt to get python 3.11.0b3 working in github actions * ci: use python 3.8 for docs check * ci: fix docs build * fix ci * mark python 3.11 tests as required * add python 3.11 to setup.py * fix docs build * remove python 3.11 test for unitcpp * remove python 3.11 test for unitcpp * remove python 3.11 test for unitcpp * attempt to fix exclude in github action Co-authored-by: Anthonios Partheniou --- .../.github/sync-repo-settings.yaml | 1 + .../proto-plus/.github/workflows/tests.yml | 23 +++++++++++++------ packages/proto-plus/docs/conf.py | 2 +- packages/proto-plus/noxfile.py | 9 +++++--- packages/proto-plus/proto/enums.py | 7 ++++-- packages/proto-plus/setup.py | 1 + .../tests/test_enum_total_ordering.py | 6 ++++- 7 files changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index dc6350a863cc..57b398e0575f 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -16,6 +16,7 @@ branchProtectionRules: - 'unit (3.9)' - 'unit (3.10, cpp)' - 'unit (3.10)' + - 'unit (3.11)' - cover - OwlBot Post Processor - 'cla/google' diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 219bb5a3d598..1e8f96368c2a 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -30,20 +30,23 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.9" - name: Install nox. run: python -m pip install nox - name: Build the documentation. run: nox -s docs unit: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] variant: ['', 'cpp', 'upb'] + exclude: + - variant: "cpp" + python: 3.11 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python }} @@ -53,16 +56,22 @@ jobs: - name: Install nox run: | pip install nox + # Trim the Python version string + - name: Trim python version + run: | + PYTHON_VERSION_TRIMMED=${{ matrix.python }} + PYTHON_VERSION_TRIMMED=$(echo $PYTHON_VERSION_TRIMMED | cut -c1-4) + echo "PYTHON_VERSION_TRIMMED=$PYTHON_VERSION_TRIMMED" >> $GITHUB_ENV - name: Run unit tests env: - COVERAGE_FILE: .coverage-${{ matrix.variant }}-${{ matrix.python }} + COVERAGE_FILE: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} run: | - nox -s unit${{ matrix.variant }}-${{ matrix.python }} + nox -s unit${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} - name: Upload coverage results uses: actions/upload-artifact@v3 with: name: coverage-artifacts - path: .coverage-${{ matrix.variant }}-${{ matrix.python }} + path: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} cover: runs-on: ubuntu-latest needs: diff --git a/packages/proto-plus/docs/conf.py b/packages/proto-plus/docs/conf.py index 4c77371ebd21..aeaf3bedee8d 100644 --- a/packages/proto-plus/docs/conf.py +++ b/packages/proto-plus/docs/conf.py @@ -64,7 +64,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 576a616c504b..e271fa87a843 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -28,8 +28,12 @@ "3.8", "3.9", "3.10", + "3.11", ] +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + @nox.session(python=PYTHON_VERSIONS) def unit(session, proto="python"): @@ -64,7 +68,7 @@ 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=PYTHON_VERSIONS) +@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) def unitcpp(session): return unit(session, proto="cpp") @@ -74,8 +78,7 @@ def unitupb(session): return unit(session, proto="upb") -# Just use the most recent version for docs -@nox.session(python=PYTHON_VERSIONS[-1]) +@nox.session(python="3.9") def docs(session): """Build the docs.""" diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 6f13d32ea201..6207ad7d1353 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -58,8 +58,11 @@ def __new__(mcls, name, bases, attrs): # In 3.7 onwards, we can define an _ignore_ attribute and do some # mucking around with that. if pb_options in attrs._member_names: - idx = attrs._member_names.index(pb_options) - attrs._member_names.pop(idx) + if isinstance(attrs._member_names, list): + idx = attrs._member_names.index(pb_options) + attrs._member_names.pop(idx) + else: # Python 3.11.0b3 + del attrs._member_names[pb_options] # Make the descriptor. enum_desc = descriptor_pb2.EnumDescriptorProto( diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index a1d51f7d6fd7..ef2de31259ed 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -55,6 +55,7 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", ], diff --git a/packages/proto-plus/tests/test_enum_total_ordering.py b/packages/proto-plus/tests/test_enum_total_ordering.py index ad7a36910bcf..584a1831c58a 100644 --- a/packages/proto-plus/tests/test_enum_total_ordering.py +++ b/packages/proto-plus/tests/test_enum_total_ordering.py @@ -49,7 +49,11 @@ def test_total_ordering_w_other_enum_type(): for item in enums_test.OtherEnum: assert not to_compare == item - assert to_compare.SOME_VALUE != item + assert type(to_compare).SOME_VALUE != item + try: + assert to_compare.SOME_VALUE != item + except AttributeError: # Python 3.11.0b3 + pass with pytest.raises(TypeError): assert not to_compare < item with pytest.raises(TypeError): From 07be5cbdc0470de03b71d5da848911371fa94da9 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 10:06:34 -0500 Subject: [PATCH 195/272] chore(main): release 1.22.2 (#343) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 12 ++++++++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 931d29c782c2..5d254c6c9a14 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [1.22.2](https://github.com/googleapis/proto-plus-python/compare/v1.22.1...v1.22.2) (2023-01-05) + + +### Bug Fixes + +* Add support for Python 3.11 ([#329](https://github.com/googleapis/proto-plus-python/issues/329)) ([5cff3a0](https://github.com/googleapis/proto-plus-python/commit/5cff3a0d703fc90f757f1d150adc0dfd62aa3d2e)) + + +### Documentation + +* Fix typo in index.rst ([#342](https://github.com/googleapis/proto-plus-python/issues/342)) ([a66a378](https://github.com/googleapis/proto-plus-python/commit/a66a3782802a1088c775a6b29adb15fa0235e1f1)) + ## [1.22.1](https://github.com/googleapis/proto-plus-python/compare/v1.22.0...v1.22.1) (2022-08-29) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index ef2de31259ed..d29352a0ff17 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.22.1" +version = "1.22.2" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From ad621ff740e43c2720e41f60d1575e9890702878 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 12 Jun 2023 13:22:13 -0400 Subject: [PATCH 196/272] fix: resolve issue where marshal fails with cross api dependency (#348) * fix: resolve issue where marshal fails with cross api dependency * Retrieve rules for cross api compatibility * add comment with link to bug --- packages/proto-plus/proto/marshal/marshal.py | 25 ++++++++++----- packages/proto-plus/tests/test_modules.py | 32 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index bd21cbc1a27e..e9fbbb9f08c1 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/proto/marshal/marshal.py @@ -159,6 +159,22 @@ def reset(self): for rule_class in stringy_numbers.STRINGY_NUMBER_RULES: self.register(rule_class._proto_type, rule_class()) + def get_rule(self, proto_type): + # Rules are needed to convert values between proto-plus and pb. + # Retrieve the rule for the specified proto type. + # The NoopRule will be used when a rule is not found. + rule = self._rules.get(proto_type, self._noop) + + # If we don't find a rule, also check under `_instances` + # in case there is a rule in another package. + # See https://github.com/googleapis/proto-plus-python/issues/349 + if rule == self._noop and hasattr(self, "_instances"): + for _, instance in self._instances.items(): + rule = instance._rules.get(proto_type, self._noop) + if rule != self._noop: + break + return rule + def to_python(self, proto_type, value, *, absent: bool = None): # Internal protobuf has its own special type for lists of values. # Return a view around it that implements MutableSequence. @@ -174,10 +190,7 @@ def to_python(self, proto_type, value, *, absent: bool = None): # Same thing for maps of messages. if value_type in compat.map_composite_types: return MapComposite(value, marshal=self) - - # Convert ordinary values. - rule = self._rules.get(proto_type, self._noop) - return rule.to_python(value, absent=absent) + return self.get_rule(proto_type=proto_type).to_python(value, absent=absent) def to_proto(self, proto_type, value, *, strict: bool = False): # The protos in google/protobuf/struct.proto are exceptional cases, @@ -212,9 +225,7 @@ def to_proto(self, proto_type, value, *, strict: bool = False): recursive_type = type(proto_type().value) return {k: self.to_proto(recursive_type, v) for k, v in value.items()} - # Convert ordinary values. - rule = self._rules.get(proto_type, self._noop) - pb_value = rule.to_proto(value) + pb_value = self.get_rule(proto_type=proto_type).to_proto(value) # Sanity check: If we are in strict mode, did we get the value we want? if strict and not isinstance(pb_value, proto_type): diff --git a/packages/proto-plus/tests/test_modules.py b/packages/proto-plus/tests/test_modules.py index 79a99da1fb00..7ab0e88741af 100644 --- a/packages/proto-plus/tests/test_modules.py +++ b/packages/proto-plus/tests/test_modules.py @@ -36,6 +36,38 @@ class Foo(proto.Message): del sys.modules[__name__].__protobuf__ +def test_module_package_cross_api(): + sys.modules[__name__].__protobuf__ = proto.module(package="spam.eggs.v1") + try: + + class Baz(proto.Message): + foo = proto.RepeatedField(proto.INT64, number=1) + + marshal = proto.Marshal(name="spam.eggs.v1") + + assert Baz.meta.package == "spam.eggs.v1" + assert Baz.pb() in marshal._rules + + sys.modules[__name__].__protobuf__ = proto.module(package="ham.pancakes.v1") + + class AnotherMessage(proto.Message): + qux = proto.Field(proto.MESSAGE, number=1, message=Baz) + + marshal = proto.Marshal(name="ham.pancakes.v1") + + assert AnotherMessage.meta.package == "ham.pancakes.v1" + assert AnotherMessage.pb() in marshal._rules + # Confirm that Baz.pb() is no longer present in marshal._rules + assert Baz.pb() not in marshal._rules + + # Test using multiple packages together + # See https://github.com/googleapis/proto-plus-python/issues/349. + msg = AnotherMessage(qux=Baz()) + assert type(msg) == AnotherMessage + finally: + del sys.modules[__name__].__protobuf__ + + def test_module_package_explicit_marshal(): sys.modules[__name__].__protobuf__ = proto.module( package="spam.eggs.v1", From 46768a06e9bbe6e963f7cc3f5b7c6a91b8af2882 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 22 Jun 2023 10:26:26 -0400 Subject: [PATCH 197/272] build: configure owlbot to copy release configuration files (#355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build: configure owlbot to copy release configuration files * style * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * delete github action to publish packages to PyPI --------- Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 17 + packages/proto-plus/.github/.OwlBot.yaml | 18 + .../.github/workflows/pypi-upload.yaml | 24 - .../proto-plus/.kokoro/docker/docs/Dockerfile | 83 +++ packages/proto-plus/.kokoro/docs/common.cfg | 67 +++ .../.kokoro/docs/docs-presubmit.cfg | 28 + packages/proto-plus/.kokoro/docs/docs.cfg | 1 + .../proto-plus/.kokoro/populate-secrets.sh | 43 ++ packages/proto-plus/.kokoro/publish-docs.sh | 62 +++ packages/proto-plus/.kokoro/release.sh | 29 ++ .../proto-plus/.kokoro/release/common.cfg | 40 ++ .../proto-plus/.kokoro/release/release.cfg | 1 + packages/proto-plus/.kokoro/requirements.in | 10 + packages/proto-plus/.kokoro/requirements.txt | 492 ++++++++++++++++++ packages/proto-plus/.kokoro/trampoline.sh | 28 + packages/proto-plus/.kokoro/trampoline_v2.sh | 487 +++++++++++++++++ packages/proto-plus/.repo-metadata.json | 12 + packages/proto-plus/.trampolinerc | 63 +++ packages/proto-plus/owlbot.py | 36 ++ 19 files changed, 1517 insertions(+), 24 deletions(-) create mode 100644 packages/proto-plus/.github/.OwlBot.lock.yaml create mode 100644 packages/proto-plus/.github/.OwlBot.yaml delete mode 100644 packages/proto-plus/.github/workflows/pypi-upload.yaml create mode 100644 packages/proto-plus/.kokoro/docker/docs/Dockerfile create mode 100644 packages/proto-plus/.kokoro/docs/common.cfg create mode 100644 packages/proto-plus/.kokoro/docs/docs-presubmit.cfg create mode 100644 packages/proto-plus/.kokoro/docs/docs.cfg create mode 100755 packages/proto-plus/.kokoro/populate-secrets.sh create mode 100755 packages/proto-plus/.kokoro/publish-docs.sh create mode 100755 packages/proto-plus/.kokoro/release.sh create mode 100644 packages/proto-plus/.kokoro/release/common.cfg create mode 100644 packages/proto-plus/.kokoro/release/release.cfg create mode 100644 packages/proto-plus/.kokoro/requirements.in create mode 100644 packages/proto-plus/.kokoro/requirements.txt create mode 100755 packages/proto-plus/.kokoro/trampoline.sh create mode 100755 packages/proto-plus/.kokoro/trampoline_v2.sh create mode 100644 packages/proto-plus/.repo-metadata.json create mode 100644 packages/proto-plus/.trampolinerc create mode 100644 packages/proto-plus/owlbot.py diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml new file mode 100644 index 000000000000..02a4dedced74 --- /dev/null +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -0,0 +1,17 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +docker: + image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest + digest: sha256:240b5bcc2bafd450912d2da2be15e62bc6de2cf839823ae4bf94d4f392b451dc +# created: 2023-06-03T21:25:37.968717478Z diff --git a/packages/proto-plus/.github/.OwlBot.yaml b/packages/proto-plus/.github/.OwlBot.yaml new file mode 100644 index 000000000000..0879a14a4334 --- /dev/null +++ b/packages/proto-plus/.github/.OwlBot.yaml @@ -0,0 +1,18 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker: + image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest + +begin-after-commit-hash: 6acf4a0a797f1082027985c55c4b14b60f673dd7 diff --git a/packages/proto-plus/.github/workflows/pypi-upload.yaml b/packages/proto-plus/.github/workflows/pypi-upload.yaml deleted file mode 100644 index edd80335260a..000000000000 --- a/packages/proto-plus/.github/workflows/pypi-upload.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Upload Python Package to PyPI - -on: - release: - types: [created] - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Install dependencies - run: python -m pip install twine wheel - - name: Package and upload modulee - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* diff --git a/packages/proto-plus/.kokoro/docker/docs/Dockerfile b/packages/proto-plus/.kokoro/docker/docs/Dockerfile new file mode 100644 index 000000000000..f8137d0ae497 --- /dev/null +++ b/packages/proto-plus/.kokoro/docker/docs/Dockerfile @@ -0,0 +1,83 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ubuntu:22.04 + +ENV DEBIAN_FRONTEND noninteractive + +# Ensure local Python is preferred over distribution Python. +ENV PATH /usr/local/bin:$PATH + +# Install dependencies. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + apt-transport-https \ + build-essential \ + ca-certificates \ + curl \ + dirmngr \ + git \ + gpg-agent \ + graphviz \ + libbz2-dev \ + libdb5.3-dev \ + libexpat1-dev \ + libffi-dev \ + liblzma-dev \ + libreadline-dev \ + libsnappy-dev \ + libssl-dev \ + libsqlite3-dev \ + portaudio19-dev \ + python3-distutils \ + redis-server \ + software-properties-common \ + ssh \ + sudo \ + tcl \ + tcl-dev \ + tk \ + tk-dev \ + uuid-dev \ + wget \ + zlib1g-dev \ + && add-apt-repository universe \ + && apt-get update \ + && apt-get -y install jq \ + && apt-get clean autoclean \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* \ + && rm -f /var/cache/apt/archives/*.deb + +###################### Install python 3.9.13 + +# Download python 3.9.13 +RUN wget https://www.python.org/ftp/python/3.9.13/Python-3.9.13.tgz + +# Extract files +RUN tar -xvf Python-3.9.13.tgz + +# Install python 3.9.13 +RUN ./Python-3.9.13/configure --enable-optimizations +RUN make altinstall + +###################### Install pip +RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ + && python3 /tmp/get-pip.py \ + && rm /tmp/get-pip.py + +# Test pip +RUN python3 -m pip + +CMD ["python3.8"] diff --git a/packages/proto-plus/.kokoro/docs/common.cfg b/packages/proto-plus/.kokoro/docs/common.cfg new file mode 100644 index 000000000000..63d9d344287d --- /dev/null +++ b/packages/proto-plus/.kokoro/docs/common.cfg @@ -0,0 +1,67 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "proto-plus-python/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-lib-docs" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/proto-plus-python/.kokoro/publish-docs.sh" +} + +env_vars: { + key: "STAGING_BUCKET" + value: "docs-staging" +} + +env_vars: { + key: "V2_STAGING_BUCKET" + # Push non-cloud library docs to `docs-staging-v2-staging` instead of the + # Cloud RAD bucket `docs-staging-v2` + value: "docs-staging-v2-staging" +} + +# It will upload the docker image after successful builds. +env_vars: { + key: "TRAMPOLINE_IMAGE_UPLOAD" + value: "true" +} + +# It will always build the docker image. +env_vars: { + key: "TRAMPOLINE_DOCKERFILE" + value: ".kokoro/docker/docs/Dockerfile" +} + +# Fetch the token needed for reporting release status to GitHub +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "yoshi-automation-github-key" + } + } +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "docuploader_service_account" + } + } +} \ No newline at end of file diff --git a/packages/proto-plus/.kokoro/docs/docs-presubmit.cfg b/packages/proto-plus/.kokoro/docs/docs-presubmit.cfg new file mode 100644 index 000000000000..f0da5bff5212 --- /dev/null +++ b/packages/proto-plus/.kokoro/docs/docs-presubmit.cfg @@ -0,0 +1,28 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "STAGING_BUCKET" + value: "gcloud-python-test" +} + +env_vars: { + key: "V2_STAGING_BUCKET" + value: "gcloud-python-test" +} + +# We only upload the image in the main `docs` build. +env_vars: { + key: "TRAMPOLINE_IMAGE_UPLOAD" + value: "false" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/proto-plus-python/.kokoro/build.sh" +} + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "docs docfx" +} diff --git a/packages/proto-plus/.kokoro/docs/docs.cfg b/packages/proto-plus/.kokoro/docs/docs.cfg new file mode 100644 index 000000000000..8f43917d92fe --- /dev/null +++ b/packages/proto-plus/.kokoro/docs/docs.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/proto-plus/.kokoro/populate-secrets.sh b/packages/proto-plus/.kokoro/populate-secrets.sh new file mode 100755 index 000000000000..f52514257ef0 --- /dev/null +++ b/packages/proto-plus/.kokoro/populate-secrets.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Copyright 2020 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} +function msg { println "$*" >&2 ;} +function println { printf '%s\n' "$(now) $*" ;} + + +# Populates requested secrets set in SECRET_MANAGER_KEYS from service account: +# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com +SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" +msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" +mkdir -p ${SECRET_LOCATION} +for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") +do + msg "Retrieving secret ${key}" + docker run --entrypoint=gcloud \ + --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \ + gcr.io/google.com/cloudsdktool/cloud-sdk \ + secrets versions access latest \ + --project cloud-devrel-kokoro-resources \ + --secret ${key} > \ + "${SECRET_LOCATION}/${key}" + if [[ $? == 0 ]]; then + msg "Secret written to ${SECRET_LOCATION}/${key}" + else + msg "Error retrieving secret ${key}" + fi +done diff --git a/packages/proto-plus/.kokoro/publish-docs.sh b/packages/proto-plus/.kokoro/publish-docs.sh new file mode 100755 index 000000000000..1c4d62370042 --- /dev/null +++ b/packages/proto-plus/.kokoro/publish-docs.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +export PATH="${HOME}/.local/bin:${PATH}" + +# Install nox +python3 -m pip install --require-hashes -r .kokoro/requirements.txt +python3 -m nox --version + +# build docs +nox -s docs + +# create metadata +python3 -m docuploader create-metadata \ + --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ + --version=$(python3 setup.py --version) \ + --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ + --distribution-name=$(python3 setup.py --name) \ + --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ + --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ + --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) + +cat docs.metadata + +# upload docs +python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" + + +# docfx yaml files +nox -s docfx + +# create metadata. +python3 -m docuploader create-metadata \ + --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ + --version=$(python3 setup.py --version) \ + --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ + --distribution-name=$(python3 setup.py --name) \ + --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ + --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ + --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) + +cat docs.metadata + +# upload docs +python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" diff --git a/packages/proto-plus/.kokoro/release.sh b/packages/proto-plus/.kokoro/release.sh new file mode 100755 index 000000000000..7d622db07b8e --- /dev/null +++ b/packages/proto-plus/.kokoro/release.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +# Start the releasetool reporter +python3 -m pip install --require-hashes -r github/proto-plus-python/.kokoro/requirements.txt +python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Move into the package, build the distribution and upload. +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") +cd github/proto-plus-python +python3 setup.py sdist bdist_wheel +twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/proto-plus/.kokoro/release/common.cfg b/packages/proto-plus/.kokoro/release/common.cfg new file mode 100644 index 000000000000..285166395242 --- /dev/null +++ b/packages/proto-plus/.kokoro/release/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "proto-plus-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/proto-plus-python/.kokoro/release.sh" +} + +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google-cloud-pypi-token-keystore-1" + } + } +} + +# Tokens needed to report release status back to GitHub +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" +} diff --git a/packages/proto-plus/.kokoro/release/release.cfg b/packages/proto-plus/.kokoro/release/release.cfg new file mode 100644 index 000000000000..8f43917d92fe --- /dev/null +++ b/packages/proto-plus/.kokoro/release/release.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/proto-plus/.kokoro/requirements.in b/packages/proto-plus/.kokoro/requirements.in new file mode 100644 index 000000000000..ec867d9fd65a --- /dev/null +++ b/packages/proto-plus/.kokoro/requirements.in @@ -0,0 +1,10 @@ +gcp-docuploader +gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x +importlib-metadata +typing-extensions +twine +wheel +setuptools +nox>=2022.11.21 # required to remove dependency on py +charset-normalizer<3 +click<8.1.0 diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt new file mode 100644 index 000000000000..c7929db6d152 --- /dev/null +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -0,0 +1,492 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes requirements.in +# +argcomplete==2.0.0 \ + --hash=sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20 \ + --hash=sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e + # via nox +attrs==22.1.0 \ + --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ + --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c + # via gcp-releasetool +bleach==5.0.1 \ + --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \ + --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c + # via readme-renderer +cachetools==5.2.0 \ + --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ + --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db + # via google-auth +certifi==2022.12.7 \ + --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ + --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 + # via requests +cffi==1.15.1 \ + --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ + --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \ + --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \ + --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \ + --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \ + --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \ + --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \ + --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \ + --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \ + --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \ + --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \ + --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \ + --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \ + --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \ + --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \ + --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \ + --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \ + --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \ + --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \ + --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \ + --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \ + --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \ + --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \ + --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \ + --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \ + --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \ + --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \ + --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \ + --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \ + --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \ + --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \ + --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \ + --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \ + --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \ + --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \ + --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \ + --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \ + --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \ + --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \ + --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \ + --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \ + --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \ + --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \ + --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \ + --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \ + --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \ + --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \ + --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \ + --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \ + --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \ + --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \ + --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \ + --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \ + --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \ + --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \ + --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \ + --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \ + --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \ + --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \ + --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \ + --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \ + --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \ + --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ + --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 + # via cryptography +charset-normalizer==2.1.1 \ + --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ + --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f + # via + # -r requirements.in + # requests +click==8.0.4 \ + --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \ + --hash=sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb + # via + # -r requirements.in + # gcp-docuploader + # gcp-releasetool +colorlog==6.7.0 \ + --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \ + --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5 + # via + # gcp-docuploader + # nox +commonmark==0.9.1 \ + --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ + --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 + # via rich +cryptography==41.0.0 \ + --hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \ + --hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \ + --hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \ + --hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \ + --hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \ + --hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \ + --hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \ + --hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \ + --hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \ + --hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \ + --hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \ + --hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \ + --hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \ + --hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \ + --hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \ + --hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \ + --hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \ + --hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \ + --hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be + # via + # gcp-releasetool + # secretstorage +distlib==0.3.6 \ + --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \ + --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e + # via virtualenv +docutils==0.19 \ + --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ + --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc + # via readme-renderer +filelock==3.8.0 \ + --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ + --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 + # via virtualenv +gcp-docuploader==0.6.4 \ + --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ + --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf + # via -r requirements.in +gcp-releasetool==1.10.5 \ + --hash=sha256:174b7b102d704b254f2a26a3eda2c684fd3543320ec239baf771542a2e58e109 \ + --hash=sha256:e29d29927fe2ca493105a82958c6873bb2b90d503acac56be2c229e74de0eec9 + # via -r requirements.in +google-api-core==2.10.2 \ + --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ + --hash=sha256:34f24bd1d5f72a8c4519773d99ca6bf080a6c4e041b4e9f024fe230191dda62e + # via + # google-cloud-core + # google-cloud-storage +google-auth==2.14.1 \ + --hash=sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d \ + --hash=sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016 + # via + # gcp-releasetool + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.3.2 \ + --hash=sha256:8417acf6466be2fa85123441696c4badda48db314c607cf1e5d543fa8bdc22fe \ + --hash=sha256:b9529ee7047fd8d4bf4a2182de619154240df17fbe60ead399078c1ae152af9a + # via google-cloud-storage +google-cloud-storage==2.6.0 \ + --hash=sha256:104ca28ae61243b637f2f01455cc8a05e8f15a2a18ced96cb587241cdd3820f5 \ + --hash=sha256:4ad0415ff61abdd8bb2ae81c1f8f7ec7d91a1011613f2db87c614c550f97bfe9 + # via gcp-docuploader +google-crc32c==1.5.0 \ + --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ + --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ + --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ + --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ + --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ + --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ + --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ + --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ + --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ + --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ + --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ + --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ + --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ + --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ + --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ + --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ + --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ + --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ + --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ + --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ + --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ + --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ + --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ + --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ + --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ + --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ + --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ + --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ + --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ + --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ + --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ + --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ + --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ + --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ + --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ + --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ + --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ + --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ + --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ + --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ + --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ + --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ + --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ + --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ + --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ + --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ + --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ + --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ + --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ + --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ + --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ + --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ + --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ + --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ + --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ + --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ + --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ + --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ + --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ + --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ + --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ + --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ + --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ + --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ + --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ + --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ + --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ + --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 + # via google-resumable-media +google-resumable-media==2.4.0 \ + --hash=sha256:2aa004c16d295c8f6c33b2b4788ba59d366677c0a25ae7382436cb30f776deaa \ + --hash=sha256:8d5518502f92b9ecc84ac46779bd4f09694ecb3ba38a3e7ca737a86d15cbca1f + # via google-cloud-storage +googleapis-common-protos==1.57.0 \ + --hash=sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46 \ + --hash=sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c + # via google-api-core +idna==3.4 \ + --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 + # via requests +importlib-metadata==5.0.0 \ + --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ + --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 + # via + # -r requirements.in + # keyring + # twine +jaraco-classes==3.2.3 \ + --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \ + --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a + # via keyring +jeepney==0.8.0 \ + --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ + --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 + # via + # keyring + # secretstorage +jinja2==3.1.2 \ + --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ + --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 + # via gcp-releasetool +keyring==23.11.0 \ + --hash=sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e \ + --hash=sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361 + # via + # gcp-releasetool + # twine +markupsafe==2.1.1 \ + --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ + --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ + --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ + --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ + --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ + --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ + --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ + --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ + --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ + --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ + --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ + --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ + --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ + --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ + --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ + --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ + --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ + --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ + --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ + --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ + --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ + --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ + --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ + --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ + --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ + --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ + --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ + --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ + --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ + --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ + --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ + --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ + --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ + --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ + --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ + --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ + --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ + --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ + --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ + --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 + # via jinja2 +more-itertools==9.0.0 \ + --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \ + --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab + # via jaraco-classes +nox==2022.11.21 \ + --hash=sha256:0e41a990e290e274cb205a976c4c97ee3c5234441a8132c8c3fd9ea3c22149eb \ + --hash=sha256:e21c31de0711d1274ca585a2c5fde36b1aa962005ba8e9322bf5eeed16dcd684 + # via -r requirements.in +packaging==21.3 \ + --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ + --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 + # via + # gcp-releasetool + # nox +pkginfo==1.8.3 \ + --hash=sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594 \ + --hash=sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c + # via twine +platformdirs==2.5.4 \ + --hash=sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7 \ + --hash=sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10 + # 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 + # via + # gcp-docuploader + # gcp-releasetool + # google-api-core +pyasn1==0.4.8 \ + --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ + --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.2.8 \ + --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ + --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 + # via google-auth +pycparser==2.21 \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + # via cffi +pygments==2.13.0 \ + --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ + --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 + # via + # readme-renderer + # rich +pyjwt==2.6.0 \ + --hash=sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd \ + --hash=sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14 + # via gcp-releasetool +pyparsing==3.0.9 \ + --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ + --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc + # via packaging +pyperclip==1.8.2 \ + --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 + # via gcp-releasetool +python-dateutil==2.8.2 \ + --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ + --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 + # via gcp-releasetool +readme-renderer==37.3 \ + --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \ + --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343 + # via twine +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 + # via + # gcp-releasetool + # google-api-core + # google-cloud-storage + # requests-toolbelt + # twine +requests-toolbelt==0.10.1 \ + --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \ + --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d + # via twine +rfc3986==2.0.0 \ + --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ + --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c + # via twine +rich==12.6.0 \ + --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ + --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 + # via twine +rsa==4.9 \ + --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ + --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 + # via google-auth +secretstorage==3.3.3 \ + --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ + --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 + # via keyring +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via + # bleach + # gcp-docuploader + # google-auth + # python-dateutil +twine==4.0.1 \ + --hash=sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e \ + --hash=sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0 + # via -r requirements.in +typing-extensions==4.4.0 \ + --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ + --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e + # via -r requirements.in +urllib3==1.26.12 \ + --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ + --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 + # via + # requests + # twine +virtualenv==20.16.7 \ + --hash=sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e \ + --hash=sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29 + # via nox +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via bleach +wheel==0.38.4 \ + --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ + --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 + # via -r requirements.in +zipp==3.10.0 \ + --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ + --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +setuptools==65.5.1 \ + --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ + --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f + # via -r requirements.in diff --git a/packages/proto-plus/.kokoro/trampoline.sh b/packages/proto-plus/.kokoro/trampoline.sh new file mode 100755 index 000000000000..f39236e943a8 --- /dev/null +++ b/packages/proto-plus/.kokoro/trampoline.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +# Always run the cleanup script, regardless of the success of bouncing into +# the container. +function cleanup() { + chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + echo "cleanup"; +} +trap cleanup EXIT + +$(dirname $0)/populate-secrets.sh # Secret Manager secrets. +python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" \ No newline at end of file diff --git a/packages/proto-plus/.kokoro/trampoline_v2.sh b/packages/proto-plus/.kokoro/trampoline_v2.sh new file mode 100755 index 000000000000..4af6cdc26dbc --- /dev/null +++ b/packages/proto-plus/.kokoro/trampoline_v2.sh @@ -0,0 +1,487 @@ +#!/usr/bin/env bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# trampoline_v2.sh +# +# This script does 3 things. +# +# 1. Prepare the Docker image for the test +# 2. Run the Docker with appropriate flags to run the test +# 3. Upload the newly built Docker image +# +# in a way that is somewhat compatible with trampoline_v1. +# +# To run this script, first download few files from gcs to /dev/shm. +# (/dev/shm is passed into the container as KOKORO_GFILE_DIR). +# +# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/secrets_viewer_service_account.json /dev/shm +# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/automl_secrets.txt /dev/shm +# +# Then run the script. +# .kokoro/trampoline_v2.sh +# +# These environment variables are required: +# TRAMPOLINE_IMAGE: The docker image to use. +# TRAMPOLINE_DOCKERFILE: The location of the Dockerfile. +# +# You can optionally change these environment variables: +# TRAMPOLINE_IMAGE_UPLOAD: +# (true|false): Whether to upload the Docker image after the +# successful builds. +# TRAMPOLINE_BUILD_FILE: The script to run in the docker container. +# TRAMPOLINE_WORKSPACE: The workspace path in the docker container. +# Defaults to /workspace. +# Potentially there are some repo specific envvars in .trampolinerc in +# the project root. + + +set -euo pipefail + +TRAMPOLINE_VERSION="2.0.5" + +if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then + readonly IO_COLOR_RED="$(tput setaf 1)" + readonly IO_COLOR_GREEN="$(tput setaf 2)" + readonly IO_COLOR_YELLOW="$(tput setaf 3)" + readonly IO_COLOR_RESET="$(tput sgr0)" +else + readonly IO_COLOR_RED="" + readonly IO_COLOR_GREEN="" + readonly IO_COLOR_YELLOW="" + readonly IO_COLOR_RESET="" +fi + +function function_exists { + [ $(LC_ALL=C type -t $1)"" == "function" ] +} + +# Logs a message using the given color. The first argument must be one +# of the IO_COLOR_* variables defined above, such as +# "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the +# given color. The log message will also have an RFC-3339 timestamp +# prepended (in UTC). You can disable the color output by setting +# TERM=vt100. +function log_impl() { + local color="$1" + shift + local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" + echo "================================================================" + echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}" + echo "================================================================" +} + +# Logs the given message with normal coloring and a timestamp. +function log() { + log_impl "${IO_COLOR_RESET}" "$@" +} + +# Logs the given message in green with a timestamp. +function log_green() { + log_impl "${IO_COLOR_GREEN}" "$@" +} + +# Logs the given message in yellow with a timestamp. +function log_yellow() { + log_impl "${IO_COLOR_YELLOW}" "$@" +} + +# Logs the given message in red with a timestamp. +function log_red() { + log_impl "${IO_COLOR_RED}" "$@" +} + +readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX) +readonly tmphome="${tmpdir}/h" +mkdir -p "${tmphome}" + +function cleanup() { + rm -rf "${tmpdir}" +} +trap cleanup EXIT + +RUNNING_IN_CI="${RUNNING_IN_CI:-false}" + +# The workspace in the container, defaults to /workspace. +TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}" + +pass_down_envvars=( + # TRAMPOLINE_V2 variables. + # Tells scripts whether they are running as part of CI or not. + "RUNNING_IN_CI" + # Indicates which CI system we're in. + "TRAMPOLINE_CI" + # Indicates the version of the script. + "TRAMPOLINE_VERSION" +) + +log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}" + +# Detect which CI systems we're in. If we're in any of the CI systems +# we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be +# the name of the CI system. Both envvars will be passing down to the +# container for telling which CI system we're in. +if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then + # descriptive env var for indicating it's on CI. + RUNNING_IN_CI="true" + TRAMPOLINE_CI="kokoro" + if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then + if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then + log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting." + exit 1 + fi + # This service account will be activated later. + TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" + else + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + gcloud auth list + fi + log_yellow "Configuring Container Registry access" + gcloud auth configure-docker --quiet + fi + pass_down_envvars+=( + # KOKORO dynamic variables. + "KOKORO_BUILD_NUMBER" + "KOKORO_BUILD_ID" + "KOKORO_JOB_NAME" + "KOKORO_GIT_COMMIT" + "KOKORO_GITHUB_COMMIT" + "KOKORO_GITHUB_PULL_REQUEST_NUMBER" + "KOKORO_GITHUB_PULL_REQUEST_COMMIT" + # For FlakyBot + "KOKORO_GITHUB_COMMIT_URL" + "KOKORO_GITHUB_PULL_REQUEST_URL" + ) +elif [[ "${TRAVIS:-}" == "true" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="travis" + pass_down_envvars+=( + "TRAVIS_BRANCH" + "TRAVIS_BUILD_ID" + "TRAVIS_BUILD_NUMBER" + "TRAVIS_BUILD_WEB_URL" + "TRAVIS_COMMIT" + "TRAVIS_COMMIT_MESSAGE" + "TRAVIS_COMMIT_RANGE" + "TRAVIS_JOB_NAME" + "TRAVIS_JOB_NUMBER" + "TRAVIS_JOB_WEB_URL" + "TRAVIS_PULL_REQUEST" + "TRAVIS_PULL_REQUEST_BRANCH" + "TRAVIS_PULL_REQUEST_SHA" + "TRAVIS_PULL_REQUEST_SLUG" + "TRAVIS_REPO_SLUG" + "TRAVIS_SECURE_ENV_VARS" + "TRAVIS_TAG" + ) +elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="github-workflow" + pass_down_envvars+=( + "GITHUB_WORKFLOW" + "GITHUB_RUN_ID" + "GITHUB_RUN_NUMBER" + "GITHUB_ACTION" + "GITHUB_ACTIONS" + "GITHUB_ACTOR" + "GITHUB_REPOSITORY" + "GITHUB_EVENT_NAME" + "GITHUB_EVENT_PATH" + "GITHUB_SHA" + "GITHUB_REF" + "GITHUB_HEAD_REF" + "GITHUB_BASE_REF" + ) +elif [[ "${CIRCLECI:-}" == "true" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="circleci" + pass_down_envvars+=( + "CIRCLE_BRANCH" + "CIRCLE_BUILD_NUM" + "CIRCLE_BUILD_URL" + "CIRCLE_COMPARE_URL" + "CIRCLE_JOB" + "CIRCLE_NODE_INDEX" + "CIRCLE_NODE_TOTAL" + "CIRCLE_PREVIOUS_BUILD_NUM" + "CIRCLE_PROJECT_REPONAME" + "CIRCLE_PROJECT_USERNAME" + "CIRCLE_REPOSITORY_URL" + "CIRCLE_SHA1" + "CIRCLE_STAGE" + "CIRCLE_USERNAME" + "CIRCLE_WORKFLOW_ID" + "CIRCLE_WORKFLOW_JOB_ID" + "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" + "CIRCLE_WORKFLOW_WORKSPACE_ID" + ) +fi + +# Configure the service account for pulling the docker image. +function repo_root() { + local dir="$1" + while [[ ! -d "${dir}/.git" ]]; do + dir="$(dirname "$dir")" + done + echo "${dir}" +} + +# Detect the project root. In CI builds, we assume the script is in +# the git tree and traverse from there, otherwise, traverse from `pwd` +# to find `.git` directory. +if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + PROGRAM_PATH="$(realpath "$0")" + PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" + PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")" +else + PROJECT_ROOT="$(repo_root $(pwd))" +fi + +log_yellow "Changing to the project root: ${PROJECT_ROOT}." +cd "${PROJECT_ROOT}" + +# To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need +# to use this environment variable in `PROJECT_ROOT`. +if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then + + mkdir -p "${tmpdir}/gcloud" + gcloud_config_dir="${tmpdir}/gcloud" + + log_yellow "Using isolated gcloud config: ${gcloud_config_dir}." + export CLOUDSDK_CONFIG="${gcloud_config_dir}" + + log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication." + gcloud auth activate-service-account \ + --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}" + log_yellow "Configuring Container Registry access" + gcloud auth configure-docker --quiet +fi + +required_envvars=( + # The basic trampoline configurations. + "TRAMPOLINE_IMAGE" + "TRAMPOLINE_BUILD_FILE" +) + +if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then + source "${PROJECT_ROOT}/.trampolinerc" +fi + +log_yellow "Checking environment variables." +for e in "${required_envvars[@]}" +do + if [[ -z "${!e:-}" ]]; then + log "Missing ${e} env var. Aborting." + exit 1 + fi +done + +# We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1 +# script: e.g. "github/repo-name/.kokoro/run_tests.sh" +TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}" +log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}" + +# ignore error on docker operations and test execution +set +e + +log_yellow "Preparing Docker image." +# We only download the docker image in CI builds. +if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + # Download the docker image specified by `TRAMPOLINE_IMAGE` + + # We may want to add --max-concurrent-downloads flag. + + log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}." + if docker pull "${TRAMPOLINE_IMAGE}"; then + log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}." + has_image="true" + else + log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}." + has_image="false" + fi +else + # For local run, check if we have the image. + if docker images "${TRAMPOLINE_IMAGE}:latest" | grep "${TRAMPOLINE_IMAGE}"; then + has_image="true" + else + has_image="false" + fi +fi + + +# The default user for a Docker container has uid 0 (root). To avoid +# creating root-owned files in the build directory we tell docker to +# use the current user ID. +user_uid="$(id -u)" +user_gid="$(id -g)" +user_name="$(id -un)" + +# To allow docker in docker, we add the user to the docker group in +# the host os. +docker_gid=$(cut -d: -f3 < <(getent group docker)) + +update_cache="false" +if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then + # Build the Docker image from the source. + context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}") + docker_build_flags=( + "-f" "${TRAMPOLINE_DOCKERFILE}" + "-t" "${TRAMPOLINE_IMAGE}" + "--build-arg" "UID=${user_uid}" + "--build-arg" "USERNAME=${user_name}" + ) + if [[ "${has_image}" == "true" ]]; then + docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}") + fi + + log_yellow "Start building the docker image." + if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then + echo "docker build" "${docker_build_flags[@]}" "${context_dir}" + fi + + # ON CI systems, we want to suppress docker build logs, only + # output the logs when it fails. + if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + if docker build "${docker_build_flags[@]}" "${context_dir}" \ + > "${tmpdir}/docker_build.log" 2>&1; then + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + cat "${tmpdir}/docker_build.log" + fi + + log_green "Finished building the docker image." + update_cache="true" + else + log_red "Failed to build the Docker image, aborting." + log_yellow "Dumping the build logs:" + cat "${tmpdir}/docker_build.log" + exit 1 + fi + else + if docker build "${docker_build_flags[@]}" "${context_dir}"; then + log_green "Finished building the docker image." + update_cache="true" + else + log_red "Failed to build the Docker image, aborting." + exit 1 + fi + fi +else + if [[ "${has_image}" != "true" ]]; then + log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting." + exit 1 + fi +fi + +# We use an array for the flags so they are easier to document. +docker_flags=( + # Remove the container after it exists. + "--rm" + + # Use the host network. + "--network=host" + + # Run in priviledged mode. We are not using docker for sandboxing or + # isolation, just for packaging our dev tools. + "--privileged" + + # Run the docker script with the user id. Because the docker image gets to + # write in ${PWD} you typically want this to be your user id. + # To allow docker in docker, we need to use docker gid on the host. + "--user" "${user_uid}:${docker_gid}" + + # Pass down the USER. + "--env" "USER=${user_name}" + + # Mount the project directory inside the Docker container. + "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}" + "--workdir" "${TRAMPOLINE_WORKSPACE}" + "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}" + + # Mount the temporary home directory. + "--volume" "${tmphome}:/h" + "--env" "HOME=/h" + + # Allow docker in docker. + "--volume" "/var/run/docker.sock:/var/run/docker.sock" + + # Mount the /tmp so that docker in docker can mount the files + # there correctly. + "--volume" "/tmp:/tmp" + # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR + # TODO(tmatsuo): This part is not portable. + "--env" "TRAMPOLINE_SECRET_DIR=/secrets" + "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile" + "--env" "KOKORO_GFILE_DIR=/secrets/gfile" + "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore" + "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore" +) + +# Add an option for nicer output if the build gets a tty. +if [[ -t 0 ]]; then + docker_flags+=("-it") +fi + +# Passing down env vars +for e in "${pass_down_envvars[@]}" +do + if [[ -n "${!e:-}" ]]; then + docker_flags+=("--env" "${e}=${!e}") + fi +done + +# If arguments are given, all arguments will become the commands run +# in the container, otherwise run TRAMPOLINE_BUILD_FILE. +if [[ $# -ge 1 ]]; then + log_yellow "Running the given commands '" "${@:1}" "' in the container." + readonly commands=("${@:1}") + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" + fi + docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" +else + log_yellow "Running the tests in a Docker container." + docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}") + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" + fi + docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" +fi + + +test_retval=$? + +if [[ ${test_retval} -eq 0 ]]; then + log_green "Build finished with ${test_retval}" +else + log_red "Build finished with ${test_retval}" +fi + +# Only upload it when the test passes. +if [[ "${update_cache}" == "true" ]] && \ + [[ $test_retval == 0 ]] && \ + [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then + log_yellow "Uploading the Docker image." + if docker push "${TRAMPOLINE_IMAGE}"; then + log_green "Finished uploading the Docker image." + else + log_red "Failed uploading the Docker image." + fi + # Call trampoline_after_upload_hook if it's defined. + if function_exists trampoline_after_upload_hook; then + trampoline_after_upload_hook + fi + +fi + +exit "${test_retval}" diff --git a/packages/proto-plus/.repo-metadata.json b/packages/proto-plus/.repo-metadata.json new file mode 100644 index 000000000000..477f07c7b2c2 --- /dev/null +++ b/packages/proto-plus/.repo-metadata.json @@ -0,0 +1,12 @@ +{ + "name": "proto-plus", + "name_pretty": "Proto Plus", + "client_documentation": "https://googleapis.dev/python/proto-plus/latest", + "release_level": "stable", + "language": "python", + "library_type": "CORE", + "repo": "googleapis/proto-plus-python", + "distribution_name": "proto-plus-python", + "default_version": "", + "codeowner_team": "" +} diff --git a/packages/proto-plus/.trampolinerc b/packages/proto-plus/.trampolinerc new file mode 100644 index 000000000000..0eee72ab62aa --- /dev/null +++ b/packages/proto-plus/.trampolinerc @@ -0,0 +1,63 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Template for .trampolinerc + +# Add required env vars here. +required_envvars+=( +) + +# Add env vars which are passed down into the container here. +pass_down_envvars+=( + "NOX_SESSION" + ############### + # Docs builds + ############### + "STAGING_BUCKET" + "V2_STAGING_BUCKET" + ################## + # Samples builds + ################## + "INSTALL_LIBRARY_FROM_SOURCE" + "RUN_TESTS_SESSION" + "BUILD_SPECIFIC_GCLOUD_PROJECT" + # Target directories. + "RUN_TESTS_DIRS" + # The nox session to run. + "RUN_TESTS_SESSION" +) + +# Prevent unintentional override on the default image. +if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ + [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then + echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." + exit 1 +fi + +# Define the default value if it makes sense. +if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then + TRAMPOLINE_IMAGE_UPLOAD="" +fi + +if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then + TRAMPOLINE_IMAGE="" +fi + +if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then + TRAMPOLINE_DOCKERFILE="" +fi + +if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then + TRAMPOLINE_BUILD_FILE="" +fi diff --git a/packages/proto-plus/owlbot.py b/packages/proto-plus/owlbot.py new file mode 100644 index 000000000000..3600950cf8a0 --- /dev/null +++ b/packages/proto-plus/owlbot.py @@ -0,0 +1,36 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This script is used to synthesize generated parts of this library.""" + +import pathlib + +import synthtool as s +from synthtool import gcp +from synthtool.languages import python + +REPO_ROOT = pathlib.Path(__file__).parent.absolute() + +common = gcp.CommonTemplates() +templated_files = common.py_library() + +excludes = [ + "continuous/**", + "presubmit/**", + "samples/**", + "build.sh", + "test-samples*.sh", +] +s.move(templated_files / ".kokoro", excludes=excludes) +s.move(templated_files / ".trampolinerc") From 31a43ef05404ff3d0e64dcc1b2184b8ad90d7351 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:21:37 -0400 Subject: [PATCH 198/272] chore(main): release 1.22.3 (#353) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 5d254c6c9a14..aba81d3a8570 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.22.3](https://github.com/googleapis/proto-plus-python/compare/v1.22.2...v1.22.3) (2023-06-22) + + +### Bug Fixes + +* Resolve issue where marshal fails with cross api dependency ([#348](https://github.com/googleapis/proto-plus-python/issues/348)) ([0dcea18](https://github.com/googleapis/proto-plus-python/commit/0dcea18898cdc2170a945f3d96216bae6a37e60f)) + ## [1.22.2](https://github.com/googleapis/proto-plus-python/compare/v1.22.1...v1.22.2) (2023-01-05) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index d29352a0ff17..670bca404dc1 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.22.2" +version = "1.22.3" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) From 8dd0e5bc32ea6f6ed5e25a310f989cbfa22084a4 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:38:23 -0400 Subject: [PATCH 199/272] chore: store artifacts in placer (#374) Source-Link: https://github.com/googleapis/synthtool/commit/cb960373d12d20f8dc38beee2bf884d49627165e Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2d816f26f728ac8b24248741e7d4c461c09764ef9f7be3684d557c9632e46dbd Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.kokoro/release/common.cfg | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 02a4dedced74..98994f474104 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:240b5bcc2bafd450912d2da2be15e62bc6de2cf839823ae4bf94d4f392b451dc -# created: 2023-06-03T21:25:37.968717478Z + digest: sha256:2d816f26f728ac8b24248741e7d4c461c09764ef9f7be3684d557c9632e46dbd +# created: 2023-06-28T17:03:33.371210701Z diff --git a/packages/proto-plus/.kokoro/release/common.cfg b/packages/proto-plus/.kokoro/release/common.cfg index 285166395242..e6c5929796b3 100644 --- a/packages/proto-plus/.kokoro/release/common.cfg +++ b/packages/proto-plus/.kokoro/release/common.cfg @@ -38,3 +38,12 @@ env_vars: { key: "SECRET_MANAGER_KEYS" value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" } + +# Store the packages we uploaded to PyPI. That way, we have a record of exactly +# what we published, which we can use to generate SBOMs and attestations. +action { + define_artifacts { + regex: "github/proto-plus-python/**/*.tar.gz" + strip_prefix: "github/proto-plus-python" + } +} From 905d40d2972e3ab8d83cba582b6dc32fccf75ed1 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 5 Jul 2023 15:22:19 -0400 Subject: [PATCH 200/272] chore: update renovate bot configuration (#358) * chore: update renovate bot configuration * fix indentation --- packages/proto-plus/renovate.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/renovate.json b/packages/proto-plus/renovate.json index f45d8f110c30..a35fc4d36c4e 100644 --- a/packages/proto-plus/renovate.json +++ b/packages/proto-plus/renovate.json @@ -1,5 +1,11 @@ { "extends": [ - "config:base" + "config:base", + "group:all", + ":disableDependencyDashboard", + "schedule:weekly" + ], + "ignorePaths": [ + ".kokoro/requirements.txt" ] } From 68c280304bfb0ad651137db5fb7d4320aafe7601 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 10 Jul 2023 15:54:09 +0200 Subject: [PATCH 201/272] chore(deps): update actions/checkout action to v3 (#375) --- packages/proto-plus/.github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 1e8f96368c2a..a2832c1276ff 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: style-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.8 uses: actions/setup-python@v4 with: @@ -29,7 +29,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.9 uses: actions/setup-python@v4 with: @@ -48,7 +48,7 @@ jobs: - variant: "cpp" python: 3.11 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v4 with: From 55451d654bc70b45a68854e2beb7d591315f8b9e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:45:55 -0400 Subject: [PATCH 202/272] build(deps): [autoapprove] bump cryptography from 41.0.0 to 41.0.2 (#377) Source-Link: https://github.com/googleapis/synthtool/commit/d6103f4a3540ba60f633a9e25c37ec5fe7e6286d Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:39f0f3f2be02ef036e297e376fe3b6256775576da8a6ccb1d5eeb80f4c8bf8fb Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- .../proto-plus/.kokoro/docker/docs/Dockerfile | 2 +- .../proto-plus/.kokoro/populate-secrets.sh | 2 +- packages/proto-plus/.kokoro/publish-docs.sh | 2 +- packages/proto-plus/.kokoro/release.sh | 2 +- packages/proto-plus/.kokoro/requirements.txt | 44 ++++++++++--------- packages/proto-plus/.kokoro/trampoline.sh | 2 +- packages/proto-plus/.kokoro/trampoline_v2.sh | 2 +- packages/proto-plus/.trampolinerc | 4 +- 9 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 98994f474104..ae4a522b9e5f 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:2d816f26f728ac8b24248741e7d4c461c09764ef9f7be3684d557c9632e46dbd -# created: 2023-06-28T17:03:33.371210701Z + digest: sha256:39f0f3f2be02ef036e297e376fe3b6256775576da8a6ccb1d5eeb80f4c8bf8fb +# created: 2023-07-17T15:20:13.819193964Z diff --git a/packages/proto-plus/.kokoro/docker/docs/Dockerfile b/packages/proto-plus/.kokoro/docker/docs/Dockerfile index f8137d0ae497..8e39a2cc438d 100644 --- a/packages/proto-plus/.kokoro/docker/docs/Dockerfile +++ b/packages/proto-plus/.kokoro/docker/docs/Dockerfile @@ -1,4 +1,4 @@ -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/proto-plus/.kokoro/populate-secrets.sh b/packages/proto-plus/.kokoro/populate-secrets.sh index f52514257ef0..6f3972140e80 100755 --- a/packages/proto-plus/.kokoro/populate-secrets.sh +++ b/packages/proto-plus/.kokoro/populate-secrets.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC. +# Copyright 2023 Google LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/proto-plus/.kokoro/publish-docs.sh b/packages/proto-plus/.kokoro/publish-docs.sh index 1c4d62370042..9eafe0be3bba 100755 --- a/packages/proto-plus/.kokoro/publish-docs.sh +++ b/packages/proto-plus/.kokoro/publish-docs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/proto-plus/.kokoro/release.sh b/packages/proto-plus/.kokoro/release.sh index 7d622db07b8e..fd9de6e90983 100755 --- a/packages/proto-plus/.kokoro/release.sh +++ b/packages/proto-plus/.kokoro/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index c7929db6d152..67d70a110897 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -113,26 +113,30 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==41.0.0 \ - --hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \ - --hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \ - --hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \ - --hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \ - --hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \ - --hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \ - --hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \ - --hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \ - --hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \ - --hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \ - --hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \ - --hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \ - --hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \ - --hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \ - --hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \ - --hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \ - --hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \ - --hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \ - --hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be +cryptography==41.0.2 \ + --hash=sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711 \ + --hash=sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7 \ + --hash=sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd \ + --hash=sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e \ + --hash=sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58 \ + --hash=sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0 \ + --hash=sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d \ + --hash=sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83 \ + --hash=sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831 \ + --hash=sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766 \ + --hash=sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b \ + --hash=sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c \ + --hash=sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182 \ + --hash=sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f \ + --hash=sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa \ + --hash=sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4 \ + --hash=sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a \ + --hash=sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2 \ + --hash=sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76 \ + --hash=sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5 \ + --hash=sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee \ + --hash=sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f \ + --hash=sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14 # via # gcp-releasetool # secretstorage diff --git a/packages/proto-plus/.kokoro/trampoline.sh b/packages/proto-plus/.kokoro/trampoline.sh index f39236e943a8..d85b1f267693 100755 --- a/packages/proto-plus/.kokoro/trampoline.sh +++ b/packages/proto-plus/.kokoro/trampoline.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 Google Inc. +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/proto-plus/.kokoro/trampoline_v2.sh b/packages/proto-plus/.kokoro/trampoline_v2.sh index 4af6cdc26dbc..59a7cf3a9373 100755 --- a/packages/proto-plus/.kokoro/trampoline_v2.sh +++ b/packages/proto-plus/.kokoro/trampoline_v2.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/proto-plus/.trampolinerc b/packages/proto-plus/.trampolinerc index 0eee72ab62aa..a7dfeb42c6d0 100644 --- a/packages/proto-plus/.trampolinerc +++ b/packages/proto-plus/.trampolinerc @@ -1,4 +1,4 @@ -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Template for .trampolinerc - # Add required env vars here. required_envvars+=( ) From ccdf6d008df4abd4a5fb8cc99d284e4533de5d6e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 21 Jul 2023 11:23:33 -0400 Subject: [PATCH 203/272] build(deps): [autoapprove] bump pygments from 2.13.0 to 2.15.0 (#379) Source-Link: https://github.com/googleapis/synthtool/commit/eaef28efd179e6eeb9f4e9bf697530d074a6f3b9 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:f8ca7655fa8a449cadcabcbce4054f593dcbae7aeeab34aa3fcc8b5cf7a93c9e Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.kokoro/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index ae4a522b9e5f..17c21d96d654 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:39f0f3f2be02ef036e297e376fe3b6256775576da8a6ccb1d5eeb80f4c8bf8fb -# created: 2023-07-17T15:20:13.819193964Z + digest: sha256:f8ca7655fa8a449cadcabcbce4054f593dcbae7aeeab34aa3fcc8b5cf7a93c9e +# created: 2023-07-21T02:12:46.49799314Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 67d70a110897..b563eb284459 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -396,9 +396,9 @@ pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pygments==2.13.0 \ - --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ - --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 +pygments==2.15.0 \ + --hash=sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094 \ + --hash=sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500 # via # readme-renderer # rich From f7624b84feaa2e6e17e76c8b5199c4a68212c98f Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 07:02:02 -0400 Subject: [PATCH 204/272] build(deps): [autoapprove] bump certifi from 2022.12.7 to 2023.7.22 (#380) Source-Link: https://github.com/googleapis/synthtool/commit/395d53adeeacfca00b73abf197f65f3c17c8f1e9 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:6c1cbc75c74b8bdd71dada2fa1677e9d6d78a889e9a70ee75b93d1d0543f96e1 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.kokoro/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 17c21d96d654..0ddd0e4d1873 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:f8ca7655fa8a449cadcabcbce4054f593dcbae7aeeab34aa3fcc8b5cf7a93c9e -# created: 2023-07-21T02:12:46.49799314Z + digest: sha256:6c1cbc75c74b8bdd71dada2fa1677e9d6d78a889e9a70ee75b93d1d0543f96e1 +# created: 2023-07-25T21:01:10.396410762Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index b563eb284459..76d9bba0f7d0 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -20,9 +20,9 @@ cachetools==5.2.0 \ --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db # via google-auth -certifi==2022.12.7 \ - --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ - --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 +certifi==2023.7.22 \ + --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 # via requests cffi==1.15.1 \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ From 8ec5bcef342a561424cc929327222637f591e336 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:46:17 -0400 Subject: [PATCH 205/272] build: [autoapprove] bump cryptography from 41.0.2 to 41.0.3 (#383) Source-Link: https://github.com/googleapis/synthtool/commit/352b9d4c068ce7c05908172af128b294073bf53c Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:3e3800bb100af5d7f9e810d48212b37812c1856d20ffeafb99ebe66461b61fc7 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- packages/proto-plus/.kokoro/requirements.txt | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 0ddd0e4d1873..a3da1b0d4cd3 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:6c1cbc75c74b8bdd71dada2fa1677e9d6d78a889e9a70ee75b93d1d0543f96e1 -# created: 2023-07-25T21:01:10.396410762Z + digest: sha256:3e3800bb100af5d7f9e810d48212b37812c1856d20ffeafb99ebe66461b61fc7 +# created: 2023-08-02T10:53:29.114535628Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 76d9bba0f7d0..029bd342de94 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -113,30 +113,30 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==41.0.2 \ - --hash=sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711 \ - --hash=sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7 \ - --hash=sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd \ - --hash=sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e \ - --hash=sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58 \ - --hash=sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0 \ - --hash=sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d \ - --hash=sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83 \ - --hash=sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831 \ - --hash=sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766 \ - --hash=sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b \ - --hash=sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c \ - --hash=sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182 \ - --hash=sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f \ - --hash=sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa \ - --hash=sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4 \ - --hash=sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a \ - --hash=sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2 \ - --hash=sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76 \ - --hash=sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5 \ - --hash=sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee \ - --hash=sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f \ - --hash=sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14 +cryptography==41.0.3 \ + --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ + --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ + --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ + --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ + --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ + --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ + --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ + --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ + --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ + --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ + --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ + --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ + --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ + --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ + --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ + --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ + --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ + --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ + --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ + --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ + --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ + --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ + --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de # via # gcp-releasetool # secretstorage From b2982c142b3dee985133172392e12a26d9db273c Mon Sep 17 00:00:00 2001 From: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:06:27 -0500 Subject: [PATCH 206/272] feat: Add additional parameters to `to_json()` and `to_dict()` methods (#384) * feat: Add additional parameters to `to_json()` and `to_dict()` methods - These parameters are just passthroughs to [`MessageToJson`][1] and [`MessageToDict`][2] [1]: https://googleapis.dev/python/protobuf/latest/google/protobuf/json_format.html#google.protobuf.json_format.MessageToJson [2]: https://googleapis.dev/python/protobuf/latest/google/protobuf/json_format.html#google.protobuf.json_format.MessageToDict * chore: Update min protobuf version to 3.20.0 to support `ensure_ascii` * Removed `ensure_ascii` to prevent version compatibility errors * Update proto/message.py Co-authored-by: Anthonios Partheniou * test: Add tests for `sort_keys` and `float_precision` * Fix test checks * Remove indent from float_precision test and add re.compile to sortkeys regex * Add spaces to float precision check, add dotall to sortkeys check * Remove indent from sort_keys test, add space to float precision test * Changed match to search * test: Add test for `to_dict()` with `float_precision` * Added TODOs for `float_precision` issue --------- Co-authored-by: Anthonios Partheniou --- packages/proto-plus/proto/message.py | 15 ++++++++++++-- packages/proto-plus/tests/test_json.py | 24 +++++++++++++++++++++++ packages/proto-plus/tests/test_message.py | 11 +++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 253e6240effc..7232d42faf93 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -376,7 +376,9 @@ def to_json( use_integers_for_enums=True, including_default_value_fields=True, preserving_proto_field_name=False, + sort_keys=False, indent=2, + float_precision=None, ) -> str: """Given a message instance, serialize it to json @@ -389,10 +391,13 @@ def to_json( 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. - indent: The JSON object will be pretty-printed with this indent level. + sort_keys (Optional(bool)): If True, then the output will be sorted by field names. + Default is False. + indent (Optional(int)): The JSON object will be pretty-printed with this indent level. An indent level of 0 or negative will only insert newlines. 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. Returns: str: The json string representation of the protocol buffer. """ @@ -401,7 +406,9 @@ def to_json( 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, ) def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": @@ -428,6 +435,7 @@ def to_dict( use_integers_for_enums=True, preserving_proto_field_name=True, including_default_value_fields=True, + float_precision=None, ) -> "Message": """Given a message instance, return its representation as a python dict. @@ -443,6 +451,8 @@ def to_dict( including_default_value_fields (Optional(bool)): An option that determines whether the default field values should be included in the results. Default is True. + float_precision (Optional(int)): If set, use this to specify float field valid digits. + Default is None. Returns: dict: A representation of the protocol buffer using pythonic data structures. @@ -454,6 +464,7 @@ def to_dict( 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, ) def copy_from(cls, instance, other): diff --git a/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index 93ca936c395c..e94e935aedd9 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/tests/test_json.py @@ -13,6 +13,7 @@ # limitations under the License. import pytest +import re import proto from google.protobuf.json_format import MessageToJson, Parse, ParseError @@ -172,3 +173,26 @@ class Squid(proto.Message): s_two = Squid.from_json(j) assert s == s_two + + +def test_json_sort_keys(): + class Squid(proto.Message): + name = proto.Field(proto.STRING, number=1) + mass_kg = proto.Field(proto.INT32, number=2) + + s = Squid(name="Steve", mass_kg=20) + j = Squid.to_json(s, sort_keys=True, indent=None) + + assert re.search(r"massKg.*name", j) + + +# TODO: https://github.com/googleapis/proto-plus-python/issues/390 +def test_json_float_precision(): + class Squid(proto.Message): + name = proto.Field(proto.STRING, number=1) + mass_kg = proto.Field(proto.FLOAT, number=2) + + s = Squid(name="Steve", mass_kg=3.14159265) + j = Squid.to_json(s, float_precision=3, indent=None) + + assert j == '{"name": "Steve", "massKg": 3.14}' diff --git a/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index 3146f0bb5a4b..983cde82135a 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/tests/test_message.py @@ -271,6 +271,17 @@ class Color(proto.Enum): assert new_s == s +# TODO: https://github.com/googleapis/proto-plus-python/issues/390 +def test_serialize_to_dict_float_precision(): + class Squid(proto.Message): + mass_kg = proto.Field(proto.FLOAT, number=1) + + s = Squid(mass_kg=3.14159265) + + s_dict = Squid.to_dict(s, float_precision=3) + assert s_dict["mass_kg"] == 3.14 + + def test_unknown_field_deserialize(): # This is a somewhat common setup: a client uses an older proto definition, # while the server sends the newer definition. The client still needs to be From 7a3f84f2335592b1031510bb24aa0a1cf4b6ff7f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 19 Sep 2023 19:05:30 +0200 Subject: [PATCH 207/272] chore(deps): update all dependencies (#392) * chore(deps): update all dependencies * See https://github.com/googleapis/proto-plus-python/issues/387 --------- Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index a2832c1276ff..9da7dff5e18e 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: style-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.8 uses: actions/setup-python@v4 with: @@ -29,7 +29,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.9 uses: actions/setup-python@v4 with: @@ -48,7 +48,7 @@ jobs: - variant: "cpp" python: 3.11 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v4 with: @@ -78,7 +78,7 @@ jobs: - unit steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: From 0deabe31a526aa06b7d3261b85fb3ea836a1a8ad Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:34:38 -0700 Subject: [PATCH 208/272] chore: [autoapprove] bump cryptography from 41.0.3 to 41.0.4 (#395) Source-Link: https://github.com/googleapis/synthtool/commit/dede53ff326079b457cfb1aae5bbdc82cbb51dc3 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:fac304457974bb530cc5396abd4ab25d26a469cd3bc97cbfb18c8d4324c584eb Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- packages/proto-plus/.kokoro/requirements.txt | 49 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index a3da1b0d4cd3..a9bdb1b7ac0f 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:3e3800bb100af5d7f9e810d48212b37812c1856d20ffeafb99ebe66461b61fc7 -# created: 2023-08-02T10:53:29.114535628Z + digest: sha256:fac304457974bb530cc5396abd4ab25d26a469cd3bc97cbfb18c8d4324c584eb +# created: 2023-10-02T21:31:03.517640371Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 029bd342de94..96d593c8c82a 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -113,30 +113,30 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==41.0.3 \ - --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ - --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ - --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ - --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ - --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ - --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ - --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ - --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ - --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ - --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ - --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ - --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ - --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ - --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ - --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ - --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ - --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ - --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ - --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ - --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ - --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ - --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ - --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de +cryptography==41.0.4 \ + --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ + --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ + --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ + --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ + --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ + --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ + --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ + --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ + --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ + --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ + --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ + --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ + --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ + --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ + --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ + --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ + --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ + --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ + --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ + --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ + --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ + --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ + --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f # via # gcp-releasetool # secretstorage @@ -382,6 +382,7 @@ protobuf==3.20.3 \ # gcp-docuploader # gcp-releasetool # google-api-core + # googleapis-common-protos pyasn1==0.4.8 \ --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba From f329fbd5bf3773cb6c3bfee49759295f5b425a04 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 10:50:55 -0400 Subject: [PATCH 209/272] chore: [autoapprove] Update `black` and `isort` to latest versions (#397) Source-Link: https://github.com/googleapis/synthtool/commit/0c7b0333f44b2b7075447f43a121a12d15a7b76a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:08e34975760f002746b1d8c86fdc90660be45945ee6d9db914d1508acdf9a547 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.kokoro/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index a9bdb1b7ac0f..dd98abbdeebe 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:fac304457974bb530cc5396abd4ab25d26a469cd3bc97cbfb18c8d4324c584eb -# created: 2023-10-02T21:31:03.517640371Z + digest: sha256:08e34975760f002746b1d8c86fdc90660be45945ee6d9db914d1508acdf9a547 +# created: 2023-10-09T14:06:13.397766266Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 96d593c8c82a..0332d3267e15 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -467,9 +467,9 @@ typing-extensions==4.4.0 \ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e # via -r requirements.in -urllib3==1.26.12 \ - --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ - --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 +urllib3==1.26.17 \ + --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ + --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b # via # requests # twine From cbe5cb4b475a67b1d0b8efa687d1172a5d42ba85 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 19 Oct 2023 13:30:02 -0400 Subject: [PATCH 210/272] feat: add support for python 3.12 (#400) * feat: add support for python 3.12 * update exclude * update noxfile * add contraints file * Disable warnings as errors * use datetime.datetime.fromtimestamp instead of datetime.datetime.utcfromtimestamp(0) which is deprecated * use datetime.timezone.utc * add comment --- packages/proto-plus/.github/workflows/tests.yml | 4 +++- packages/proto-plus/noxfile.py | 8 +++++++- packages/proto-plus/proto/datetime_helpers.py | 3 ++- packages/proto-plus/setup.py | 1 + packages/proto-plus/testing/constraints-3.12.txt | 0 5 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 packages/proto-plus/testing/constraints-3.12.txt diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 9da7dff5e18e..e28374fee20d 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -42,11 +42,13 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] variant: ['', 'cpp', 'upb'] exclude: - variant: "cpp" python: 3.11 + - variant: "cpp" + python: 3.12 steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index e271fa87a843..6addf9ed6d01 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -29,6 +29,7 @@ "3.9", "3.10", "3.11", + "3.12", ] # Error if a python version is missing @@ -48,9 +49,14 @@ def unit(session, proto="python"): session.install("-e", ".[testing]", "-c", constraints_path) if proto == "cpp": # 4.20 does not have cpp. session.install("protobuf==3.19.0") + + # 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/googleapis/proto-plus-python/issues/403 is fixed. + # See also https://github.com/protocolbuffers/protobuf/issues/12186. session.run( "py.test", - "-W=error", "--quiet", *( session.posargs # Coverage info when running individual tests is annoying. diff --git a/packages/proto-plus/proto/datetime_helpers.py b/packages/proto-plus/proto/datetime_helpers.py index 7a3aafec68ea..ffac4f47d95a 100644 --- a/packages/proto-plus/proto/datetime_helpers.py +++ b/packages/proto-plus/proto/datetime_helpers.py @@ -21,7 +21,8 @@ from google.protobuf import timestamp_pb2 -_UTC_EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=datetime.timezone.utc) +_UTC_EPOCH = datetime.datetime.fromtimestamp(0, datetime.timezone.utc) + _RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ" _RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S" # datetime.strptime cannot handle nanosecond precision: parse w/ regex diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 670bca404dc1..759494f46a1a 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -56,6 +56,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", ], diff --git a/packages/proto-plus/testing/constraints-3.12.txt b/packages/proto-plus/testing/constraints-3.12.txt new file mode 100644 index 000000000000..e69de29bb2d1 From b847605fe96fb8803f5ac7a03322253e2a07d1f8 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Thu, 19 Oct 2023 13:50:31 -0400 Subject: [PATCH 211/272] build: update the list of required status check contexts (#404) Co-authored-by: omair --- packages/proto-plus/.github/sync-repo-settings.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index 57b398e0575f..f106858920a5 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -11,12 +11,20 @@ branchProtectionRules: - 'unit (3.6, cpp)' - 'unit (3.7)' - 'unit (3.7, cpp)' + - 'unit (3.7, upb)' - 'unit (3.8)' - - 'unit (3.9, cpp)' + - 'unit (3.8, cpp)' + - 'unit (3.8, upb)' - 'unit (3.9)' - - 'unit (3.10, cpp)' + - 'unit (3.9, cpp)' + - 'unit (3.9, upb)' - 'unit (3.10)' + - 'unit (3.10, cpp)' + - 'unit (3.10, upb)' - 'unit (3.11)' + - 'unit (3.11, upb)' + - 'unit (3.12)' + - 'unit (3.12, upb)' - cover - OwlBot Post Processor - 'cla/google' From d00032f7ec3a336e68ad8d234051e024df2a504f Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:53:54 +0000 Subject: [PATCH 212/272] chore: rename rst files to avoid conflict with service names (#399) Source-Link: https://github.com/googleapis/synthtool/commit/d52e638b37b091054c869bfa6f5a9fedaba9e0dd Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:4f9b3b106ad0beafc2c8a415e3f62c1a0cc23cabea115dbe841b848f581cfe99 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.kokoro/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index dd98abbdeebe..7f291dbd5f9b 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:08e34975760f002746b1d8c86fdc90660be45945ee6d9db914d1508acdf9a547 -# created: 2023-10-09T14:06:13.397766266Z + digest: sha256:4f9b3b106ad0beafc2c8a415e3f62c1a0cc23cabea115dbe841b848f581cfe99 +# created: 2023-10-18T20:26:37.410353675Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 0332d3267e15..16170d0ca7b8 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -467,9 +467,9 @@ typing-extensions==4.4.0 \ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e # via -r requirements.in -urllib3==1.26.17 \ - --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ - --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b +urllib3==1.26.18 \ + --hash=sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07 \ + --hash=sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0 # via # requests # twine From 703f4319ebb3e78a0efe926d1ae44b91c36776e0 Mon Sep 17 00:00:00 2001 From: Victor Chudnovsky Date: Thu, 19 Oct 2023 11:12:15 -0700 Subject: [PATCH 213/272] docs: clarify TODO for reenabling erroring on warnings (#405) --- packages/proto-plus/noxfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 6addf9ed6d01..c6a850c21357 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -50,11 +50,11 @@ def unit(session, proto="python"): if proto == "cpp": # 4.20 does not have cpp. session.install("protobuf==3.19.0") - # The warnings as errors flag `-W=error` was removed in + # 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/googleapis/proto-plus-python/issues/403 is fixed. - # See also https://github.com/protocolbuffers/protobuf/issues/12186. + # https://github.com/protocolbuffers/protobuf/issues/12186 is fixed. session.run( "py.test", "--quiet", From 9e691a4349676df631ce33f5d44686eae7857ab2 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 12:51:28 -0500 Subject: [PATCH 214/272] chore: bump urllib3 from 1.26.12 to 1.26.18 (#406) Source-Link: https://github.com/googleapis/synthtool/commit/febacccc98d6d224aff9d0bd0373bb5a4cd5969c Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:caffe0a9277daeccc4d1de5c9b55ebba0901b57c2f713ec9c876b0d4ec064f61 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- packages/proto-plus/.kokoro/requirements.txt | 532 +++++++++--------- 2 files changed, 277 insertions(+), 259 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 7f291dbd5f9b..453b540c1e58 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:4f9b3b106ad0beafc2c8a415e3f62c1a0cc23cabea115dbe841b848f581cfe99 -# created: 2023-10-18T20:26:37.410353675Z + digest: sha256:caffe0a9277daeccc4d1de5c9b55ebba0901b57c2f713ec9c876b0d4ec064f61 +# created: 2023-11-08T19:46:45.022803742Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 16170d0ca7b8..8957e21104e2 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -4,91 +4,75 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==2.0.0 \ - --hash=sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20 \ - --hash=sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e +argcomplete==3.1.4 \ + --hash=sha256:72558ba729e4c468572609817226fb0a6e7e9a0a7d477b882be168c0b4a62b94 \ + --hash=sha256:fbe56f8cda08aa9a04b307d8482ea703e96a6a801611acb4be9bf3942017989f # via nox -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via gcp-releasetool -bleach==5.0.1 \ - --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \ - --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c - # via readme-renderer -cachetools==5.2.0 \ - --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ - --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db +cachetools==5.3.2 \ + --hash=sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2 \ + --hash=sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1 # via google-auth certifi==2023.7.22 \ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 # via requests -cffi==1.15.1 \ - --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ - --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \ - --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \ - --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \ - --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \ - --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \ - --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \ - --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \ - --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \ - --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \ - --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \ - --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \ - --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \ - --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \ - --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \ - --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \ - --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \ - --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \ - --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \ - --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \ - --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \ - --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \ - --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \ - --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \ - --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \ - --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \ - --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \ - --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \ - --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \ - --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \ - --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \ - --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \ - --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \ - --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \ - --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \ - --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \ - --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \ - --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \ - --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \ - --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \ - --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \ - --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \ - --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \ - --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \ - --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \ - --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \ - --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \ - --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \ - --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \ - --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \ - --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \ - --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \ - --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \ - --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \ - --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \ - --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \ - --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \ - --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \ - --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \ - --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \ - --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \ - --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \ - --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ - --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 +cffi==1.16.0 \ + --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ + --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ + --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ + --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ + --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ + --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ + --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ + --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ + --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ + --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ + --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ + --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ + --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ + --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ + --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ + --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ + --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ + --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ + --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ + --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ + --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ + --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ + --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ + --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ + --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ + --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ + --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ + --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ + --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ + --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ + --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ + --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ + --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ + --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ + --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ + --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ + --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ + --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ + --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ + --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ + --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ + --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ + --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ + --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ + --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ + --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ + --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ + --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ + --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ + --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ + --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ + --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 # via cryptography charset-normalizer==2.1.1 \ --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ @@ -109,78 +93,74 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -commonmark==0.9.1 \ - --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ - --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 - # via rich -cryptography==41.0.4 \ - --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ - --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ - --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ - --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ - --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ - --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ - --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ - --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ - --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ - --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ - --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ - --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ - --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ - --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ - --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ - --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ - --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ - --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ - --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ - --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ - --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ - --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ - --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f +cryptography==41.0.5 \ + --hash=sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf \ + --hash=sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84 \ + --hash=sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e \ + --hash=sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8 \ + --hash=sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7 \ + --hash=sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1 \ + --hash=sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88 \ + --hash=sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86 \ + --hash=sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179 \ + --hash=sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81 \ + --hash=sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20 \ + --hash=sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548 \ + --hash=sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d \ + --hash=sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d \ + --hash=sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5 \ + --hash=sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1 \ + --hash=sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147 \ + --hash=sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936 \ + --hash=sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797 \ + --hash=sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696 \ + --hash=sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72 \ + --hash=sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da \ + --hash=sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723 # via # gcp-releasetool # secretstorage -distlib==0.3.6 \ - --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \ - --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e +distlib==0.3.7 \ + --hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \ + --hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8 # via virtualenv -docutils==0.19 \ - --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ - --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc +docutils==0.20.1 \ + --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ + --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b # via readme-renderer -filelock==3.8.0 \ - --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ - --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 +filelock==3.13.1 \ + --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ + --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c # via virtualenv -gcp-docuploader==0.6.4 \ - --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ - --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf +gcp-docuploader==0.6.5 \ + --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ + --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==1.10.5 \ - --hash=sha256:174b7b102d704b254f2a26a3eda2c684fd3543320ec239baf771542a2e58e109 \ - --hash=sha256:e29d29927fe2ca493105a82958c6873bb2b90d503acac56be2c229e74de0eec9 +gcp-releasetool==1.16.0 \ + --hash=sha256:27bf19d2e87aaa884096ff941aa3c592c482be3d6a2bfe6f06afafa6af2353e3 \ + --hash=sha256:a316b197a543fd036209d0caba7a8eb4d236d8e65381c80cbc6d7efaa7606d63 # via -r requirements.in -google-api-core==2.10.2 \ - --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ - --hash=sha256:34f24bd1d5f72a8c4519773d99ca6bf080a6c4e041b4e9f024fe230191dda62e +google-api-core==2.12.0 \ + --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ + --hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160 # via # google-cloud-core # google-cloud-storage -google-auth==2.14.1 \ - --hash=sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d \ - --hash=sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016 +google-auth==2.23.4 \ + --hash=sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3 \ + --hash=sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2 # via # gcp-releasetool # google-api-core # google-cloud-core # google-cloud-storage -google-cloud-core==2.3.2 \ - --hash=sha256:8417acf6466be2fa85123441696c4badda48db314c607cf1e5d543fa8bdc22fe \ - --hash=sha256:b9529ee7047fd8d4bf4a2182de619154240df17fbe60ead399078c1ae152af9a +google-cloud-core==2.3.3 \ + --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ + --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 # via google-cloud-storage -google-cloud-storage==2.6.0 \ - --hash=sha256:104ca28ae61243b637f2f01455cc8a05e8f15a2a18ced96cb587241cdd3820f5 \ - --hash=sha256:4ad0415ff61abdd8bb2ae81c1f8f7ec7d91a1011613f2db87c614c550f97bfe9 +google-cloud-storage==2.13.0 \ + --hash=sha256:ab0bf2e1780a1b74cf17fccb13788070b729f50c252f0c94ada2aae0ca95437d \ + --hash=sha256:f62dc4c7b6cd4360d072e3deb28035fbdad491ac3d9b0b1815a12daea10f37c7 # via gcp-docuploader google-crc32c==1.5.0 \ --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ @@ -251,29 +231,31 @@ google-crc32c==1.5.0 \ --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 - # via google-resumable-media -google-resumable-media==2.4.0 \ - --hash=sha256:2aa004c16d295c8f6c33b2b4788ba59d366677c0a25ae7382436cb30f776deaa \ - --hash=sha256:8d5518502f92b9ecc84ac46779bd4f09694ecb3ba38a3e7ca737a86d15cbca1f + # via + # google-cloud-storage + # google-resumable-media +google-resumable-media==2.6.0 \ + --hash=sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7 \ + --hash=sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b # via google-cloud-storage -googleapis-common-protos==1.57.0 \ - --hash=sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46 \ - --hash=sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c +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 # via requests -importlib-metadata==5.0.0 \ - --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ - --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 +importlib-metadata==6.8.0 \ + --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ + --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 # via # -r requirements.in # keyring # twine -jaraco-classes==3.2.3 \ - --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \ - --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a +jaraco-classes==3.3.0 \ + --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \ + --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -285,75 +267,121 @@ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via gcp-releasetool -keyring==23.11.0 \ - --hash=sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e \ - --hash=sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361 +keyring==24.2.0 \ + --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ + --hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509 # via # gcp-releasetool # twine -markupsafe==2.1.1 \ - --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ - --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ - --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ - --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ - --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ - --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ - --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ - --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ - --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ - --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ - --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ - --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ - --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ - --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ - --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ - --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ - --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ - --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ - --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ - --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ - --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ - --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ - --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ - --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ - --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ - --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ - --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ - --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ - --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ - --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ - --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ - --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ - --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ - --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ - --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ - --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ - --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ - --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ - --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ - --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ + --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ + --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 # via jinja2 -more-itertools==9.0.0 \ - --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \ - --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +more-itertools==10.1.0 \ + --hash=sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a \ + --hash=sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6 # via jaraco-classes -nox==2022.11.21 \ - --hash=sha256:0e41a990e290e274cb205a976c4c97ee3c5234441a8132c8c3fd9ea3c22149eb \ - --hash=sha256:e21c31de0711d1274ca585a2c5fde36b1aa962005ba8e9322bf5eeed16dcd684 +nh3==0.2.14 \ + --hash=sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873 \ + --hash=sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad \ + --hash=sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5 \ + --hash=sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525 \ + --hash=sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2 \ + --hash=sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e \ + --hash=sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d \ + --hash=sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450 \ + --hash=sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e \ + --hash=sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6 \ + --hash=sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a \ + --hash=sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4 \ + --hash=sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4 \ + --hash=sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6 \ + --hash=sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e \ + --hash=sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75 + # via readme-renderer +nox==2023.4.22 \ + --hash=sha256:0b1adc619c58ab4fa57d6ab2e7823fe47a32e70202f287d78474adcc7bda1891 \ + --hash=sha256:46c0560b0dc609d7d967dc99e22cb463d3c4caf54a5fda735d6c11b5177e3a9f # via -r requirements.in -packaging==21.3 \ - --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ - --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 +packaging==23.2 \ + --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ + --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via # gcp-releasetool # nox -pkginfo==1.8.3 \ - --hash=sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594 \ - --hash=sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c +pkginfo==1.9.6 \ + --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \ + --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046 # via twine -platformdirs==2.5.4 \ - --hash=sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7 \ - --hash=sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10 +platformdirs==3.11.0 \ + --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ + --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e # via virtualenv protobuf==3.20.3 \ --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ @@ -383,34 +411,30 @@ protobuf==3.20.3 \ # gcp-releasetool # google-api-core # googleapis-common-protos -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba +pyasn1==0.5.0 \ + --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ + --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde # via # pyasn1-modules # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 +pyasn1-modules==0.3.0 \ + --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ + --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d # via google-auth pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pygments==2.15.0 \ - --hash=sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094 \ - --hash=sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500 +pygments==2.16.1 \ + --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ + --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 # via # readme-renderer # rich -pyjwt==2.6.0 \ - --hash=sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd \ - --hash=sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14 +pyjwt==2.8.0 \ + --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ + --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 # via gcp-releasetool -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc - # via packaging pyperclip==1.8.2 \ --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 # via gcp-releasetool @@ -418,9 +442,9 @@ python-dateutil==2.8.2 \ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 # via gcp-releasetool -readme-renderer==37.3 \ - --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \ - --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343 +readme-renderer==42.0 \ + --hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \ + --hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1 # via twine requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ @@ -431,17 +455,17 @@ requests==2.31.0 \ # google-cloud-storage # requests-toolbelt # twine -requests-toolbelt==0.10.1 \ - --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \ - --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d +requests-toolbelt==1.0.0 \ + --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ + --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 # via twine rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==12.6.0 \ - --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ - --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 +rich==13.6.0 \ + --hash=sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245 \ + --hash=sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ @@ -455,43 +479,37 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via - # bleach # gcp-docuploader - # google-auth # python-dateutil -twine==4.0.1 \ - --hash=sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e \ - --hash=sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0 +twine==4.0.2 \ + --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \ + --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8 # via -r requirements.in -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e +typing-extensions==4.8.0 \ + --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ + --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef # via -r requirements.in -urllib3==1.26.18 \ - --hash=sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07 \ - --hash=sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0 +urllib3==2.0.7 \ + --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \ + --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e # via # requests # twine -virtualenv==20.16.7 \ - --hash=sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e \ - --hash=sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29 +virtualenv==20.24.6 \ + --hash=sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af \ + --hash=sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381 # via nox -webencodings==0.5.1 \ - --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ - --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 - # via bleach -wheel==0.38.4 \ - --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ - --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 +wheel==0.41.3 \ + --hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \ + --hash=sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841 # via -r requirements.in -zipp==3.10.0 \ - --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ - --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 +zipp==3.17.0 \ + --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ + --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==65.5.1 \ - --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ - --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f +setuptools==68.2.2 \ + --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ + --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a # via -r requirements.in From a86e311dfdf4cfbc64602e7daa585400b007e53a Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 21 Nov 2023 15:56:18 -0500 Subject: [PATCH 215/272] docs: add documentation on how to query the current oneof in a given message (#408) --- packages/proto-plus/docs/fields.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/proto-plus/docs/fields.rst b/packages/proto-plus/docs/fields.rst index b930c61a43fa..1961cff07407 100644 --- a/packages/proto-plus/docs/fields.rst +++ b/packages/proto-plus/docs/fields.rst @@ -177,6 +177,23 @@ a string (which should match for all fields within the oneof): s = Song(database_id="e6aa708c7e", name="Little Fugue") assert "name" in s and "database_id" not in s + To query which ``oneof`` is present in a given message, use ``proto.Message._pb("oneof")``. + + Example: + + .. code-block:: python + + import proto + + class Song(proto.Message): + name = proto.Field(proto.STRING, number=1, oneof="identifier") + database_id = proto.Field(proto.STRING, number=2, oneof="identifier") + + s = Song(name="Canon in D minor") + assert s._pb.WhichOneof("identifier") == "name" + + s = Song(database_id="e6aa708c7e") + assert s._pb.WhichOneof("identifier") == "database_id" Optional fields From 7ea15b30b528dc265bbf836fe6802078282fcbcd Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 21 Nov 2023 15:56:41 -0500 Subject: [PATCH 216/272] feat: add support for proto.__version__ (#393) --- packages/proto-plus/proto/__init__.py | 2 ++ packages/proto-plus/proto/version.py | 15 +++++++++++++++ packages/proto-plus/setup.py | 10 ++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 packages/proto-plus/proto/version.py diff --git a/packages/proto-plus/proto/__init__.py b/packages/proto-plus/proto/__init__.py index 80c7948b5ced..8780991c0426 100644 --- a/packages/proto-plus/proto/__init__.py +++ b/packages/proto-plus/proto/__init__.py @@ -20,6 +20,7 @@ from .message import Message from .modules import define_module as module from .primitives import ProtoType +from .version import __version__ DOUBLE = ProtoType.DOUBLE @@ -42,6 +43,7 @@ __all__ = ( + "__version__", "Enum", "Field", "MapField", diff --git a/packages/proto-plus/proto/version.py b/packages/proto-plus/proto/version.py new file mode 100644 index 000000000000..28b55af1a079 --- /dev/null +++ b/packages/proto-plus/proto/version.py @@ -0,0 +1,15 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +__version__ = "1.22.3" diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 759494f46a1a..71468a2df096 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -13,14 +13,20 @@ # limitations under the License. import io +import re import os from setuptools import find_packages, setup -version = "1.22.3" - PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) +version = None + +with open(os.path.join(PACKAGE_ROOT, "proto/version.py")) as fp: + version_candidates = re.findall(r"(?<=\")\d+.\d+.\d+(?=\")", fp.read()) + assert len(version_candidates) == 1 + version = version_candidates[0] + with io.open(os.path.join(PACKAGE_ROOT, "README.rst")) as file_obj: README = file_obj.read() From 1fada8c5afc9ef5ac54d9e9fe348281b3ec4f79e Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 21 Nov 2023 16:42:15 -0500 Subject: [PATCH 217/272] docs: add example for __protobuf__ module level attribute (#409) --- packages/proto-plus/docs/messages.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/proto-plus/docs/messages.rst b/packages/proto-plus/docs/messages.rst index e033473c02f5..68d7d63d4d7f 100644 --- a/packages/proto-plus/docs/messages.rst +++ b/packages/proto-plus/docs/messages.rst @@ -55,6 +55,33 @@ A few things to note: falsy value or not set all is to mark it ``optional``. * Because all fields are optional, it is the responsibility of application logic to determine whether a necessary field has been set. +* You can optionally define a `__protobuf__` attribute in your module which will be used + to differentiate messages which have the same name but exist in different modules. + +.. code-block:: python + + # file a.py + import proto + + __protobuf__ = proto.module(package="a") + + class A(proto.Message): + name = proto.Field(proto.STRING, number=1) + + # file b.py + import proto + + __protobuf__ = proto.module(package="b") + + class A(proto.Message): + name = proto.Field(proto.STRING, number=1) + + # file main.py + import a + import b + + _a = a.A(name="Hello, A!") + _b = b.A(name="Hello, B!") .. _messages: https://developers.google.com/protocol-buffers/docs/proto3#simple From 198da28cbad300485185f23fdd8c189e5c3bdd01 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 28 Nov 2023 15:49:52 -0500 Subject: [PATCH 218/272] build: remove pkg_resources from docs/conf.py (#411) --- packages/proto-plus/docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/docs/conf.py b/packages/proto-plus/docs/conf.py index aeaf3bedee8d..aced5d30752f 100644 --- a/packages/proto-plus/docs/conf.py +++ b/packages/proto-plus/docs/conf.py @@ -15,7 +15,7 @@ import os import sys -import pkg_resources +import proto sys.path.insert(0, os.path.abspath("..")) @@ -27,7 +27,7 @@ copyright = "2018, Google LLC" author = "Google LLC" -version = pkg_resources.get_distribution("proto-plus").version +version = proto.version.__version__ # -- General configuration --------------------------------------------------- From 2479361f4af57302e7ed6d5545af2d3737eceacf Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 28 Nov 2023 15:50:21 -0500 Subject: [PATCH 219/272] chore: update CODEOWNERS to python-core-client-libraries (#410) --- packages/proto-plus/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.github/CODEOWNERS b/packages/proto-plus/.github/CODEOWNERS index 0546e1d81418..6f558ad8337f 100644 --- a/packages/proto-plus/.github/CODEOWNERS +++ b/packages/proto-plus/.github/CODEOWNERS @@ -4,4 +4,4 @@ # For syntax help see: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax -* @googleapis/actools-python @googleapis/yoshi-python +* @googleapis/python-core-client-libraries From 33dcf95746a2d6477451dd720afdae488138aba1 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 30 Nov 2023 17:06:26 -0500 Subject: [PATCH 220/272] fix: use setuptools.find_namespace_packages (#412) --- packages/proto-plus/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 71468a2df096..796f0a913d8f 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -16,7 +16,7 @@ import re import os -from setuptools import find_packages, setup +from setuptools import find_namespace_packages, setup PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) @@ -37,7 +37,7 @@ author="Google LLC", author_email="googleapis-packages@google.com", url="https://github.com/googleapis/proto-plus-python.git", - packages=find_packages(exclude=["docs", "tests"]), + packages=find_namespace_packages(exclude=["docs", "tests"]), description="Beautiful, Pythonic protocol buffers.", long_description=README, platforms="Posix; MacOS X", From 359c2ccc6fb7c760c8730231aa7a1862a832cba2 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:06:56 -0500 Subject: [PATCH 221/272] chore: bump cryptography from 41.0.5 to 41.0.6 in /synthtool/gcp/templates/python_library/.kokoro (#414) Source-Link: https://github.com/googleapis/synthtool/commit/9367caadcbb30b5b2719f30eb00c44cc913550ed Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2f155882785883336b4468d5218db737bb1d10c9cea7cb62219ad16fe248c03c Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- packages/proto-plus/.kokoro/requirements.txt | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 453b540c1e58..773c1dfd2146 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:caffe0a9277daeccc4d1de5c9b55ebba0901b57c2f713ec9c876b0d4ec064f61 -# created: 2023-11-08T19:46:45.022803742Z + digest: sha256:2f155882785883336b4468d5218db737bb1d10c9cea7cb62219ad16fe248c03c +# created: 2023-11-29T14:54:29.548172703Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 8957e21104e2..e5c1ffca94b7 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -93,30 +93,30 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==41.0.5 \ - --hash=sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf \ - --hash=sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84 \ - --hash=sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e \ - --hash=sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8 \ - --hash=sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7 \ - --hash=sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1 \ - --hash=sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88 \ - --hash=sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86 \ - --hash=sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179 \ - --hash=sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81 \ - --hash=sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20 \ - --hash=sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548 \ - --hash=sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d \ - --hash=sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d \ - --hash=sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5 \ - --hash=sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1 \ - --hash=sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147 \ - --hash=sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936 \ - --hash=sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797 \ - --hash=sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696 \ - --hash=sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72 \ - --hash=sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da \ - --hash=sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723 +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 # via # gcp-releasetool # secretstorage From 769ac27f313eb315ba3d37b98cc9440f111bcc32 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Fri, 1 Dec 2023 15:10:33 -0500 Subject: [PATCH 222/272] build: treat warnings as errors (#415) --- packages/proto-plus/pytest.ini | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/proto-plus/pytest.ini diff --git a/packages/proto-plus/pytest.ini b/packages/proto-plus/pytest.ini new file mode 100644 index 000000000000..985c25595d2c --- /dev/null +++ b/packages/proto-plus/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +filterwarnings = + # treat all warnings as errors + error + # Remove once https://github.com/protocolbuffers/protobuf/issues/12186 is fixed + ignore:.*custom tp_new.*in Python 3.14:DeprecationWarning From 94aa913e1b3649d4aafde6b3c4d453dc58cc849d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:08:12 -0500 Subject: [PATCH 223/272] chore(main): release 1.23.0 (#391) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 20 ++++++++++++++++++++ packages/proto-plus/proto/version.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index aba81d3a8570..a22bc0637ca2 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.23.0](https://github.com/googleapis/proto-plus-python/compare/v1.22.3...v1.23.0) (2023-12-01) + + +### Features + +* Add additional parameters to `to_json()` and `to_dict()` methods ([#384](https://github.com/googleapis/proto-plus-python/issues/384)) ([8f13a46](https://github.com/googleapis/proto-plus-python/commit/8f13a46514e1d7653426c0db3c1021f9c794451a)) +* Add support for proto.__version__ ([#393](https://github.com/googleapis/proto-plus-python/issues/393)) ([48cd63f](https://github.com/googleapis/proto-plus-python/commit/48cd63f2d0a7c62c40d2724f46ac564c9884675b)) +* Add support for python 3.12 ([#400](https://github.com/googleapis/proto-plus-python/issues/400)) ([1b3a96f](https://github.com/googleapis/proto-plus-python/commit/1b3a96fae7a21bf0120a79ba6bf57aacfd2a0db4)) + + +### Bug Fixes + +* Use setuptools.find_namespace_packages ([#412](https://github.com/googleapis/proto-plus-python/issues/412)) ([30a6864](https://github.com/googleapis/proto-plus-python/commit/30a6864739eb8fb116caa5873044d3999f37f578)) + + +### Documentation + +* Add documentation on how to query the current oneof in a given message ([#408](https://github.com/googleapis/proto-plus-python/issues/408)) ([d89d811](https://github.com/googleapis/proto-plus-python/commit/d89d81112885f3b3ca4e1342fd2034ee6797fcf6)) +* Add example for __protobuf__ module level attribute ([#409](https://github.com/googleapis/proto-plus-python/issues/409)) ([6755884](https://github.com/googleapis/proto-plus-python/commit/675588450acc4636b2d82b2bc0860a314064c4a4)) + ## [1.22.3](https://github.com/googleapis/proto-plus-python/compare/v1.22.2...v1.22.3) (2023-06-22) diff --git a/packages/proto-plus/proto/version.py b/packages/proto-plus/proto/version.py index 28b55af1a079..f11d15fbb34d 100644 --- a/packages/proto-plus/proto/version.py +++ b/packages/proto-plus/proto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.22.3" +__version__ = "1.23.0" From fa08db9b41b567841d0d35fcc7fe6b34ccc352cf Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 14 Dec 2023 13:34:05 +0100 Subject: [PATCH 224/272] chore(deps): update all dependencies (#418) * chore(deps): update all dependencies * revert --------- Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index e28374fee20d..9fc10045f4dc 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.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. @@ -52,7 +52,7 @@ jobs: 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 }} - name: Install nox @@ -82,7 +82,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 From 5238bebde9ba77af4cfc9c968a44117bf638809c Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 8 Jan 2024 15:06:19 -0500 Subject: [PATCH 225/272] chore: fix distribution name in .repo-metadata.json (#417) --- packages/proto-plus/.repo-metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/.repo-metadata.json b/packages/proto-plus/.repo-metadata.json index 477f07c7b2c2..cbe8a78e01b2 100644 --- a/packages/proto-plus/.repo-metadata.json +++ b/packages/proto-plus/.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": "" } From 2293d30dfda09e35d029eb4f44a18c477ee2bc3e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:38:43 +0000 Subject: [PATCH 226/272] build(python): fix `docs` and `docfx` builds (#422) * build(python): fix `docs` and `docfx` builds Source-Link: https://github.com/googleapis/synthtool/commit/fac8444edd5f5526e804c306b766a271772a3e2f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:5ea6d0ab82c956b50962f91d94e206d3921537ae5fe1549ec5326381d8905cfa * See https://github.com/googleapis/synthtool/pull/1916 --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/.OwlBot.lock.yaml | 6 +++--- packages/proto-plus/.kokoro/requirements.txt | 6 +++--- packages/proto-plus/noxfile.py | 14 +++++++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 773c1dfd2146..d8a1bbca7179 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.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:5ea6d0ab82c956b50962f91d94e206d3921537ae5fe1549ec5326381d8905cfa +# created: 2024-01-15T16:32:08.142785673Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index e5c1ffca94b7..bb3d6ca38b14 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -263,9 +263,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 \ diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index c6a850c21357..d4da4b9b4abf 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -88,7 +88,19 @@ def unitupb(session): 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! From cd6f2bbb84b2990097440ca153feb2d2063344e8 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 25 Jan 2024 13:43:11 +0100 Subject: [PATCH 227/272] chore(deps): update all dependencies (#419) * chore(deps): update all dependencies * See googleapis/synthtool#1910 * See https://github.com/googleapis/synthtool/pull/1910 --------- Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 9fc10045f4dc..4fad78dcac59 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -70,9 +70,9 @@ jobs: run: | nox -s unit${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} - 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 }} cover: runs-on: ubuntu-latest @@ -90,11 +90,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 From b34ddb5f2ade899044ec321bcc8dbb037b5d0797 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:12:38 -0400 Subject: [PATCH 228/272] build(deps): bump cryptography from 42.0.2 to 42.0.4 in .kokoro (#436) Source-Link: https://github.com/googleapis/synthtool/commit/d895aec3679ad22aa120481f746bf9f2f325f26f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:98f3afd11308259de6e828e37376d18867fd321aba07826e29e4f8d9cab56bad Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- packages/proto-plus/.kokoro/requirements.txt | 57 +++++++++++-------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index d8a1bbca7179..e4e943e0259a 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:5ea6d0ab82c956b50962f91d94e206d3921537ae5fe1549ec5326381d8905cfa -# created: 2024-01-15T16:32:08.142785673Z + digest: sha256:98f3afd11308259de6e828e37376d18867fd321aba07826e29e4f8d9cab56bad +# created: 2024-02-27T15:56:18.442440378Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index bb3d6ca38b14..bda8e38c4f31 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -93,30 +93,39 @@ 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.4 \ + --hash=sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b \ + --hash=sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce \ + --hash=sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88 \ + --hash=sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7 \ + --hash=sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20 \ + --hash=sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9 \ + --hash=sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff \ + --hash=sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1 \ + --hash=sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764 \ + --hash=sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b \ + --hash=sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298 \ + --hash=sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1 \ + --hash=sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824 \ + --hash=sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257 \ + --hash=sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a \ + --hash=sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129 \ + --hash=sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb \ + --hash=sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929 \ + --hash=sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854 \ + --hash=sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52 \ + --hash=sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923 \ + --hash=sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885 \ + --hash=sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0 \ + --hash=sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd \ + --hash=sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2 \ + --hash=sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18 \ + --hash=sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b \ + --hash=sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992 \ + --hash=sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74 \ + --hash=sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660 \ + --hash=sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925 \ + --hash=sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449 # via # gcp-releasetool # secretstorage From f1daaeb91a8ad425869dd19106613755ccdcc092 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 04:12:39 -0400 Subject: [PATCH 229/272] chore(python): update dependencies in /.kokoro (#438) Source-Link: https://github.com/googleapis/synthtool/commit/db94845da69ccdfefd7ce55c84e6cfa74829747e Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:a8a80fc6456e433df53fc2a0d72ca0345db0ddefb409f1b75b118dfd1babd952 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- .../proto-plus/.kokoro/docker/docs/Dockerfile | 4 + .../.kokoro/docker/docs/requirements.in | 1 + .../.kokoro/docker/docs/requirements.txt | 38 ++++++ packages/proto-plus/.kokoro/requirements.in | 3 +- packages/proto-plus/.kokoro/requirements.txt | 114 ++++++++---------- 6 files changed, 99 insertions(+), 65 deletions(-) create mode 100644 packages/proto-plus/.kokoro/docker/docs/requirements.in create mode 100644 packages/proto-plus/.kokoro/docker/docs/requirements.txt diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index e4e943e0259a..4bdeef3904e2 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:98f3afd11308259de6e828e37376d18867fd321aba07826e29e4f8d9cab56bad -# created: 2024-02-27T15:56:18.442440378Z + digest: sha256:a8a80fc6456e433df53fc2a0d72ca0345db0ddefb409f1b75b118dfd1babd952 +# created: 2024-03-15T16:25:47.905264637Z diff --git a/packages/proto-plus/.kokoro/docker/docs/Dockerfile b/packages/proto-plus/.kokoro/docker/docs/Dockerfile index 8e39a2cc438d..bdaf39fe22d0 100644 --- a/packages/proto-plus/.kokoro/docker/docs/Dockerfile +++ b/packages/proto-plus/.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/packages/proto-plus/.kokoro/docker/docs/requirements.in b/packages/proto-plus/.kokoro/docker/docs/requirements.in new file mode 100644 index 000000000000..816817c672a1 --- /dev/null +++ b/packages/proto-plus/.kokoro/docker/docs/requirements.in @@ -0,0 +1 @@ +nox diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.txt b/packages/proto-plus/.kokoro/docker/docs/requirements.txt new file mode 100644 index 000000000000..0e5d70f20f83 --- /dev/null +++ b/packages/proto-plus/.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/packages/proto-plus/.kokoro/requirements.in b/packages/proto-plus/.kokoro/requirements.in index ec867d9fd65a..fff4d9ce0d0a 100644 --- a/packages/proto-plus/.kokoro/requirements.in +++ b/packages/proto-plus/.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/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index bda8e38c4f31..dd61f5f32018 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -93,40 +93,41 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==42.0.4 \ - --hash=sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b \ - --hash=sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce \ - --hash=sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88 \ - --hash=sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7 \ - --hash=sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20 \ - --hash=sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9 \ - --hash=sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff \ - --hash=sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1 \ - --hash=sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764 \ - --hash=sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b \ - --hash=sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298 \ - --hash=sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1 \ - --hash=sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824 \ - --hash=sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257 \ - --hash=sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a \ - --hash=sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129 \ - --hash=sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb \ - --hash=sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929 \ - --hash=sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854 \ - --hash=sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52 \ - --hash=sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923 \ - --hash=sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885 \ - --hash=sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0 \ - --hash=sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd \ - --hash=sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2 \ - --hash=sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18 \ - --hash=sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b \ - --hash=sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992 \ - --hash=sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74 \ - --hash=sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660 \ - --hash=sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925 \ - --hash=sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449 +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 \ @@ -145,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 \ @@ -392,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 @@ -518,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 From b6f5db82f0265735e70642460abc494a81bd9d90 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:43:52 -0400 Subject: [PATCH 230/272] chore(python): bump idna from 3.4 to 3.7 in .kokoro (#443) Source-Link: https://github.com/googleapis/synthtool/commit/d50980e704793a2d3310bfb3664f3a82f24b5796 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.kokoro/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 4bdeef3904e2..81f87c56917d 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:a8a80fc6456e433df53fc2a0d72ca0345db0ddefb409f1b75b118dfd1babd952 -# created: 2024-03-15T16:25:47.905264637Z + digest: sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 +# created: 2024-04-12T11:35:58.922854369Z diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index dd61f5f32018..51f92b8e12f1 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -252,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 \ From 96e5c072b5e926929bf0b323ad9e02c5d7431b07 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 4 Jun 2024 07:01:38 -0400 Subject: [PATCH 231/272] fix: Add compatibility with protobuf==5.x (#433) * ci: add support for pre-release sessions * add compatibility with protobuf 5.x * fix RecursionError: maximum recursion depth exceeded while calling a Python object in tests * update required checks * update comments * update required checks * See https://github.com/protocolbuffers/protobuf/issues/16596;avoid breaking change * lint * Address review feedback * lint * add tests * Update tests/test_fields_mitigate_collision.py * add deprecation warning * Cater for protobuf 5.x+ * style * Filter deprecation warning * remove redundant code * revert * add comment * add comment * refactor code * style * update warning filter * address review comments * map_composite_types_str->map_composite_type_names * update comment * lint * add more test cases in test_json_default_values * add more test cases to tests/test_message.py * address review feedback * add comment * formatting * formatting * typo * Address review feedback * Update proto/marshal/marshal.py Co-authored-by: Victor Chudnovsky * typo * address review feedback * address review feedback * fix test case * stye * add more test cases * add test case * Update tests/test_message.py Co-authored-by: Victor Chudnovsky --------- Co-authored-by: Victor Chudnovsky --- .../.github/sync-repo-settings.yaml | 17 +- .../proto-plus/.github/workflows/tests.yml | 40 +++- packages/proto-plus/noxfile.py | 80 ++++++-- packages/proto-plus/proto/marshal/compat.py | 19 +- packages/proto-plus/proto/marshal/marshal.py | 9 +- packages/proto-plus/proto/message.py | 188 ++++++++++++++++-- packages/proto-plus/pytest.ini | 2 + packages/proto-plus/setup.py | 2 +- .../proto-plus/testing/constraints-3.13.txt | 0 packages/proto-plus/tests/conftest.py | 16 +- packages/proto-plus/tests/test_json.py | 73 ++++++- packages/proto-plus/tests/test_message.py | 59 +++++- 12 files changed, 447 insertions(+), 58 deletions(-) create mode 100644 packages/proto-plus/testing/constraints-3.13.txt diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index f106858920a5..1bb27933db3d 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -7,24 +7,27 @@ branchProtectionRules: requiredStatusCheckContexts: - 'style-check' - 'docs' - - 'unit (3.6)' - 'unit (3.6, cpp)' - - 'unit (3.7)' + - 'unit (3.6, python)' + - 'unit (3.6, upb)' - '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/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 4fad78dcac59..03f198965dd0 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -42,19 +42,27 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] - variant: ['', 'cpp', 'upb'] + python: ['3.6', '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@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@v4 with: name: coverage-artifact-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} path: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} + prerelease: + runs-on: ubuntu-20.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: diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index d4da4b9b4abf..4ba0161adfb0 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -13,10 +13,9 @@ # limitations under the License. from __future__ import absolute_import -import os -import pathlib import nox +import pathlib CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() @@ -30,6 +29,7 @@ "3.10", "3.11", "3.12", + "3.13", ] # Error if a python version is missing @@ -37,26 +37,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,17 +75,59 @@ 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") diff --git a/packages/proto-plus/proto/marshal/compat.py b/packages/proto-plus/proto/marshal/compat.py index e10999bbad2c..dc77f3ac067b 100644 --- a/packages/proto-plus/proto/marshal/compat.py +++ b/packages/proto-plus/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/packages/proto-plus/proto/marshal/marshal.py b/packages/proto-plus/proto/marshal/marshal.py index e9fbbb9f08c1..d278421a5797 100644 --- a/packages/proto-plus/proto/marshal/marshal.py +++ b/packages/proto-plus/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/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 7232d42faf93..2772fb42b4a8 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/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/packages/proto-plus/pytest.ini b/packages/proto-plus/pytest.ini index 985c25595d2c..ba89114a1d71 100644 --- a/packages/proto-plus/pytest.ini +++ b/packages/proto-plus/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/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 796f0a913d8f..1de209412586 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -45,7 +45,7 @@ install_requires=("protobuf >= 3.19.0, <5.0.0dev",), extras_require={ "testing": [ - "google-api-core[grpc] >= 1.31.5", + "google-api-core >= 1.31.5", ], }, python_requires=">=3.6", diff --git a/packages/proto-plus/testing/constraints-3.13.txt b/packages/proto-plus/testing/constraints-3.13.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/proto-plus/tests/conftest.py b/packages/proto-plus/tests/conftest.py index 765326c674ef..9f2f5c9540ad 100644 --- a/packages/proto-plus/tests/conftest.py +++ b/packages/proto-plus/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/packages/proto-plus/tests/test_json.py b/packages/proto-plus/tests/test_json.py index e94e935aedd9..ae3cf59eeb2e 100644 --- a/packages/proto-plus/tests/test_json.py +++ b/packages/proto-plus/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/packages/proto-plus/tests/test_message.py b/packages/proto-plus/tests/test_message.py index 983cde82135a..720995a90787 100644 --- a/packages/proto-plus/tests/test_message.py +++ b/packages/proto-plus/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 From 540b4155f9772ba7c20f01a614677f81a3ad9192 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 11 Jun 2024 12:33:12 -0400 Subject: [PATCH 232/272] fix(deps): allow protobuf 5.x (#457) --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 1de209412586..255d9fa55dd7 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -42,7 +42,7 @@ 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 >= 1.31.5", From 753de08e414d7ae19a85440ed41700476c1dbcf1 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 11 Jun 2024 12:33:57 -0400 Subject: [PATCH 233/272] fix: drop python 3.6 (#456) * fix: drop python 3.6 * remove testing/constraints-3.6.txt * update version in .readthedocs.yml --- packages/proto-plus/.github/sync-repo-settings.yaml | 3 --- packages/proto-plus/.github/workflows/tests.yml | 2 +- packages/proto-plus/.readthedocs.yml | 2 +- packages/proto-plus/noxfile.py | 1 - packages/proto-plus/setup.py | 3 +-- packages/proto-plus/testing/constraints-3.6.txt | 9 --------- 6 files changed, 3 insertions(+), 17 deletions(-) delete mode 100644 packages/proto-plus/testing/constraints-3.6.txt diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index 1bb27933db3d..d85818a50d92 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -7,9 +7,6 @@ branchProtectionRules: requiredStatusCheckContexts: - 'style-check' - 'docs' - - 'unit (3.6, cpp)' - - 'unit (3.6, python)' - - 'unit (3.6, upb)' - 'unit (3.7, cpp)' - 'unit (3.7, python)' - 'unit (3.7, upb)' diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 03f198965dd0..59518accd19d 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + 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. diff --git a/packages/proto-plus/.readthedocs.yml b/packages/proto-plus/.readthedocs.yml index ebea21ff0c06..309b5e76a26c 100644 --- a/packages/proto-plus/.readthedocs.yml +++ b/packages/proto-plus/.readthedocs.yml @@ -3,4 +3,4 @@ build: image: latest python: pip_install: true - version: 3.6 + version: 3.9 diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 4ba0161adfb0..19c80c9525ad 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -22,7 +22,6 @@ PYTHON_VERSIONS = [ - "3.6", "3.7", "3.8", "3.9", diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 255d9fa55dd7..cb09371fa92d 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -48,7 +48,7 @@ "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/packages/proto-plus/testing/constraints-3.6.txt b/packages/proto-plus/testing/constraints-3.6.txt deleted file mode 100644 index 849c8e7cb244..000000000000 --- a/packages/proto-plus/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 From 16f38471c620257685c4168dd409752be57f0b3d Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 11 Jun 2024 18:37:06 +0200 Subject: [PATCH 234/272] chore(deps): update all dependencies (#455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix: allow protobuf <6 --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/workflows/tests.yml | 4 ++-- packages/proto-plus/setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 59518accd19d..edbc2141b572 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: - name: Build the documentation. run: nox -s docs unit: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] @@ -83,7 +83,7 @@ jobs: name: coverage-artifact-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} path: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} prerelease: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: python: ['3.12'] diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index cb09371fa92d..e3a548921974 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -42,7 +42,7 @@ long_description=README, platforms="Posix; MacOS X", include_package_data=True, - install_requires=("protobuf >= 3.19.0, <6.0.0dev",), + install_requires=("protobuf >=3.19.0, <6.0.0dev",), extras_require={ "testing": [ "google-api-core >= 1.31.5", From 01a5df82cc4ea9fbfaacce63eddab665169b26dc Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:31:56 -0400 Subject: [PATCH 235/272] chore(main): release 1.24.0 (#453) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Anthonios Partheniou --- packages/proto-plus/CHANGELOG.md | 20 ++++++++++++++++++++ packages/proto-plus/proto/version.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index a22bc0637ca2..2171e424265f 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/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/packages/proto-plus/proto/version.py b/packages/proto-plus/proto/version.py index f11d15fbb34d..dbdc76e064e4 100644 --- a/packages/proto-plus/proto/version.py +++ b/packages/proto-plus/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" From 0df25fad225f17d1e75b7ee7e0367be405c16e3e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:00:42 -0400 Subject: [PATCH 236/272] chore: update templated files (#464) Source-Link: https://github.com/googleapis/synthtool/commit/a37f74cd300d1f56d6f28c368d2931f72adee948 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:d3de8a02819f65001effcbd3ea76ce97e9bcff035c7a89457f40f892c87c5b32 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- .../proto-plus/.kokoro/docker/docs/Dockerfile | 2 +- .../proto-plus/.kokoro/populate-secrets.sh | 2 +- packages/proto-plus/.kokoro/publish-docs.sh | 2 +- packages/proto-plus/.kokoro/release.sh | 2 +- packages/proto-plus/.kokoro/requirements.txt | 509 +++++++++--------- packages/proto-plus/.kokoro/trampoline.sh | 2 +- packages/proto-plus/.kokoro/trampoline_v2.sh | 2 +- packages/proto-plus/.trampolinerc | 2 +- 9 files changed, 275 insertions(+), 252 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 81f87c56917d..91d742b5b9fe 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 -# created: 2024-04-12T11:35:58.922854369Z + digest: sha256:d3de8a02819f65001effcbd3ea76ce97e9bcff035c7a89457f40f892c87c5b32 +# created: 2024-07-03T17:43:00.77142528Z diff --git a/packages/proto-plus/.kokoro/docker/docs/Dockerfile b/packages/proto-plus/.kokoro/docker/docs/Dockerfile index bdaf39fe22d0..a26ce61930f5 100644 --- a/packages/proto-plus/.kokoro/docker/docs/Dockerfile +++ b/packages/proto-plus/.kokoro/docker/docs/Dockerfile @@ -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. diff --git a/packages/proto-plus/.kokoro/populate-secrets.sh b/packages/proto-plus/.kokoro/populate-secrets.sh index 6f3972140e80..c435402f473e 100755 --- a/packages/proto-plus/.kokoro/populate-secrets.sh +++ b/packages/proto-plus/.kokoro/populate-secrets.sh @@ -1,5 +1,5 @@ #!/bin/bash -# 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. diff --git a/packages/proto-plus/.kokoro/publish-docs.sh b/packages/proto-plus/.kokoro/publish-docs.sh index 9eafe0be3bba..38f083f05aa0 100755 --- a/packages/proto-plus/.kokoro/publish-docs.sh +++ b/packages/proto-plus/.kokoro/publish-docs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# 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. diff --git a/packages/proto-plus/.kokoro/release.sh b/packages/proto-plus/.kokoro/release.sh index fd9de6e90983..2083f94788b4 100755 --- a/packages/proto-plus/.kokoro/release.sh +++ b/packages/proto-plus/.kokoro/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -# 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. diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 51f92b8e12f1..35ece0e4d2e9 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -4,21 +4,25 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.1.4 \ - --hash=sha256:72558ba729e4c468572609817226fb0a6e7e9a0a7d477b882be168c0b4a62b94 \ - --hash=sha256:fbe56f8cda08aa9a04b307d8482ea703e96a6a801611acb4be9bf3942017989f +argcomplete==3.4.0 \ + --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ + --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f # via nox -attrs==23.1.0 \ - --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ - --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 +attrs==23.2.0 \ + --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ + --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via gcp-releasetool -cachetools==5.3.2 \ - --hash=sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2 \ - --hash=sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1 +backports-tarfile==1.2.0 \ + --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ + --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 + # via jaraco-context +cachetools==5.3.3 \ + --hash=sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945 \ + --hash=sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105 # via google-auth -certifi==2023.7.22 \ - --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ - --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 +certifi==2024.6.2 \ + --hash=sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516 \ + --hash=sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56 # via requests cffi==1.16.0 \ --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ @@ -87,90 +91,90 @@ click==8.0.4 \ # -r requirements.in # gcp-docuploader # gcp-releasetool -colorlog==6.7.0 \ - --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \ - --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5 +colorlog==6.8.2 \ + --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ + --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 # via # gcp-docuploader # nox -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 +cryptography==42.0.8 \ + --hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \ + --hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \ + --hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \ + --hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \ + --hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \ + --hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \ + --hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \ + --hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \ + --hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \ + --hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \ + --hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \ + --hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \ + --hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \ + --hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \ + --hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \ + --hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \ + --hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \ + --hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \ + --hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \ + --hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \ + --hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \ + --hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \ + --hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \ + --hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \ + --hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \ + --hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \ + --hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \ + --hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \ + --hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \ + --hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \ + --hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \ + --hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e # via # -r requirements.in # gcp-releasetool # secretstorage -distlib==0.3.7 \ - --hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \ - --hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8 +distlib==0.3.8 \ + --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ + --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 # via virtualenv -docutils==0.20.1 \ - --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ - --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b +docutils==0.21.2 \ + --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ + --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 # via readme-renderer -filelock==3.13.1 \ - --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ - --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c +filelock==3.15.4 \ + --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ + --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 # via virtualenv gcp-docuploader==0.6.5 \ --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==2.0.0 \ - --hash=sha256:3d73480b50ba243f22d7c7ec08b115a30e1c7817c4899781840c26f9c55b8277 \ - --hash=sha256:7aa9fd935ec61e581eb8458ad00823786d91756c25e492f372b2b30962f3c28f +gcp-releasetool==2.0.1 \ + --hash=sha256:34314a910c08e8911d9c965bd44f8f2185c4f556e737d719c33a41f6a610de96 \ + --hash=sha256:b0d5863c6a070702b10883d37c4bdfd74bf930fe417f36c0c965d3b7c779ae62 # via -r requirements.in -google-api-core==2.12.0 \ - --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ - --hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160 +google-api-core==2.19.1 \ + --hash=sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125 \ + --hash=sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd # via # google-cloud-core # google-cloud-storage -google-auth==2.23.4 \ - --hash=sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3 \ - --hash=sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2 +google-auth==2.31.0 \ + --hash=sha256:042c4702efa9f7d3c48d3a69341c209381b125faa6dbf3ebe56bc7e40ae05c23 \ + --hash=sha256:87805c36970047247c8afe614d4e3af8eceafc1ebba0c679fe75ddd1d575e871 # via # gcp-releasetool # google-api-core # google-cloud-core # google-cloud-storage -google-cloud-core==2.3.3 \ - --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ - --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 +google-cloud-core==2.4.1 \ + --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ + --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 # via google-cloud-storage -google-cloud-storage==2.13.0 \ - --hash=sha256:ab0bf2e1780a1b74cf17fccb13788070b729f50c252f0c94ada2aae0ca95437d \ - --hash=sha256:f62dc4c7b6cd4360d072e3deb28035fbdad491ac3d9b0b1815a12daea10f37c7 +google-cloud-storage==2.17.0 \ + --hash=sha256:49378abff54ef656b52dca5ef0f2eba9aa83dc2b2c72c78714b03a1a95fe9388 \ + --hash=sha256:5b393bc766b7a3bc6f5407b9e665b2450d36282614b7945e570b3480a456d1e1 # via gcp-docuploader google-crc32c==1.5.0 \ --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ @@ -244,28 +248,36 @@ google-crc32c==1.5.0 \ # via # google-cloud-storage # google-resumable-media -google-resumable-media==2.6.0 \ - --hash=sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7 \ - --hash=sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b +google-resumable-media==2.7.1 \ + --hash=sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c \ + --hash=sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33 # via google-cloud-storage -googleapis-common-protos==1.61.0 \ - --hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \ - --hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b +googleapis-common-protos==1.63.2 \ + --hash=sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945 \ + --hash=sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87 # via google-api-core idna==3.7 \ --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 # via requests -importlib-metadata==6.8.0 \ - --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ - --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 +importlib-metadata==8.0.0 \ + --hash=sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f \ + --hash=sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812 # via # -r requirements.in # keyring # twine -jaraco-classes==3.3.0 \ - --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \ - --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621 +jaraco-classes==3.4.0 \ + --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ + --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 + # via keyring +jaraco-context==5.3.0 \ + --hash=sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266 \ + --hash=sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2 + # via keyring +jaraco-functools==4.0.1 \ + --hash=sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664 \ + --hash=sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -273,13 +285,13 @@ jeepney==0.8.0 \ # via # keyring # secretstorage -jinja2==3.1.3 \ - --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ - --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 +jinja2==3.1.4 \ + --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ + --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via gcp-releasetool -keyring==24.2.0 \ - --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ - --hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509 +keyring==25.2.1 \ + --hash=sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50 \ + --hash=sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b # via # gcp-releasetool # twine @@ -287,146 +299,153 @@ markdown-it-py==3.0.0 \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb # via rich -markupsafe==2.1.3 \ - --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ - --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ - --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ - --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ - --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ - --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ - --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ - --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ - --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ - --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ - --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ - --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ - --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ - --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ - --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ - --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ - --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ - --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ - --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ - --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ - --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ - --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ - --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ - --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ - --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ - --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ - --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ - --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ - --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ - --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ - --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ - --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ - --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ - --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ - --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ - --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ - --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ - --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ - --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ - --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ - --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ - --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ - --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ - --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ - --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ - --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ - --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ - --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ - --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ - --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ - --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ - --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ - --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ - --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ - --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ - --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ - --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ - --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ - --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ - --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 +markupsafe==2.1.5 \ + --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ + --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ + --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ + --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ + --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ + --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ + --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ + --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ + --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ + --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ + --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ + --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ + --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ + --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ + --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ + --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ + --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ + --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ + --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ + --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ + --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ + --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ + --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ + --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ + --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ + --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ + --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ + --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ + --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ + --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ + --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ + --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ + --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ + --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ + --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ + --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ + --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ + --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ + --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ + --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ + --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ + --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ + --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ + --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ + --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ + --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ + --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ + --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ + --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ + --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ + --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ + --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ + --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ + --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ + --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ + --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ + --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ + --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ + --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ + --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 # via jinja2 mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py -more-itertools==10.1.0 \ - --hash=sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a \ - --hash=sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6 - # via jaraco-classes -nh3==0.2.14 \ - --hash=sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873 \ - --hash=sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad \ - --hash=sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5 \ - --hash=sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525 \ - --hash=sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2 \ - --hash=sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e \ - --hash=sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d \ - --hash=sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450 \ - --hash=sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e \ - --hash=sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6 \ - --hash=sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a \ - --hash=sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4 \ - --hash=sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4 \ - --hash=sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6 \ - --hash=sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e \ - --hash=sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75 +more-itertools==10.3.0 \ + --hash=sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463 \ + --hash=sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320 + # via + # jaraco-classes + # jaraco-functools +nh3==0.2.17 \ + --hash=sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a \ + --hash=sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911 \ + --hash=sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb \ + --hash=sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a \ + --hash=sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc \ + --hash=sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028 \ + --hash=sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9 \ + --hash=sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3 \ + --hash=sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351 \ + --hash=sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10 \ + --hash=sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71 \ + --hash=sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f \ + --hash=sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b \ + --hash=sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a \ + --hash=sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062 \ + --hash=sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a # via readme-renderer -nox==2023.4.22 \ - --hash=sha256:0b1adc619c58ab4fa57d6ab2e7823fe47a32e70202f287d78474adcc7bda1891 \ - --hash=sha256:46c0560b0dc609d7d967dc99e22cb463d3c4caf54a5fda735d6c11b5177e3a9f +nox==2024.4.15 \ + --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ + --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f # via -r requirements.in -packaging==23.2 \ - --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ - --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via # gcp-releasetool # nox -pkginfo==1.9.6 \ - --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \ - --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046 +pkginfo==1.10.0 \ + --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ + --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 # via twine -platformdirs==3.11.0 \ - --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ - --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 # via virtualenv -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 +proto-plus==1.24.0 \ + --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ + --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 + # via google-api-core +protobuf==5.27.2 \ + --hash=sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505 \ + --hash=sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b \ + --hash=sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38 \ + --hash=sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863 \ + --hash=sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470 \ + --hash=sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6 \ + --hash=sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce \ + --hash=sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca \ + --hash=sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5 \ + --hash=sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e \ + --hash=sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714 # via # gcp-docuploader # gcp-releasetool # google-api-core # googleapis-common-protos -pyasn1==0.5.0 \ - --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ - --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde + # proto-plus +pyasn1==0.6.0 \ + --hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \ + --hash=sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473 # via # pyasn1-modules # rsa -pyasn1-modules==0.3.0 \ - --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ - --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d +pyasn1-modules==0.4.0 \ + --hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \ + --hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b # via google-auth -pycparser==2.21 \ - --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ - --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 +pycparser==2.22 \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -pygments==2.16.1 \ - --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ - --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 +pygments==2.18.0 \ + --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ + --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a # via # readme-renderer # rich @@ -434,20 +453,20 @@ pyjwt==2.8.0 \ --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 # via gcp-releasetool -pyperclip==1.8.2 \ - --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 +pyperclip==1.9.0 \ + --hash=sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310 # via gcp-releasetool -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via gcp-releasetool -readme-renderer==42.0 \ - --hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \ - --hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1 +readme-renderer==43.0 \ + --hash=sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311 \ + --hash=sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9 # via twine -requests==2.31.0 \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 +requests==2.32.3 \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 # via # gcp-releasetool # google-api-core @@ -462,9 +481,9 @@ rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==13.6.0 \ - --hash=sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245 \ - --hash=sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef +rich==13.7.1 \ + --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ + --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ @@ -480,35 +499,39 @@ six==1.16.0 \ # via # gcp-docuploader # python-dateutil -twine==4.0.2 \ - --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \ - --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8 +tomli==2.0.1 \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f + # via nox +twine==5.1.1 \ + --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ + --hash=sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db # via -r requirements.in -typing-extensions==4.8.0 \ - --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ - --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 # via -r requirements.in -urllib3==2.0.7 \ - --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \ - --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e +urllib3==2.2.2 \ + --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ + --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 # via # requests # twine -virtualenv==20.24.6 \ - --hash=sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af \ - --hash=sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381 +virtualenv==20.26.3 \ + --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ + --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 # via nox -wheel==0.41.3 \ - --hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \ - --hash=sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841 +wheel==0.43.0 \ + --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ + --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 # via -r requirements.in -zipp==3.17.0 \ - --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ - --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 +zipp==3.19.2 \ + --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ + --hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==69.2.0 \ - --hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \ - --hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c +setuptools==70.2.0 \ + --hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05 \ + --hash=sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1 # via -r requirements.in diff --git a/packages/proto-plus/.kokoro/trampoline.sh b/packages/proto-plus/.kokoro/trampoline.sh index d85b1f267693..48f79699706e 100755 --- a/packages/proto-plus/.kokoro/trampoline.sh +++ b/packages/proto-plus/.kokoro/trampoline.sh @@ -1,5 +1,5 @@ #!/bin/bash -# 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. diff --git a/packages/proto-plus/.kokoro/trampoline_v2.sh b/packages/proto-plus/.kokoro/trampoline_v2.sh index 59a7cf3a9373..35fa529231dc 100755 --- a/packages/proto-plus/.kokoro/trampoline_v2.sh +++ b/packages/proto-plus/.kokoro/trampoline_v2.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# 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. diff --git a/packages/proto-plus/.trampolinerc b/packages/proto-plus/.trampolinerc index a7dfeb42c6d0..0080152373d5 100644 --- a/packages/proto-plus/.trampolinerc +++ b/packages/proto-plus/.trampolinerc @@ -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. From 979932df5b6c99ed22ddfcfac48512d41e08536d Mon Sep 17 00:00:00 2001 From: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Date: Thu, 11 Jul 2024 12:17:17 -0500 Subject: [PATCH 237/272] =?UTF-8?q?fix:=20Update=20message.py=20spelling?= =?UTF-8?q?=20error=20`paylod`=20=E2=86=92=20`payload`=20(#472)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/proto-plus/proto/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 2772fb42b4a8..97e745101ef1 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -537,7 +537,7 @@ def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": parse it into a message. Args: - paylod: A json string representing a message. + payload: A json string representing a message. ignore_unknown_fields (Optional(bool)): If True, do not raise errors for unknown fields. From e735b3adf0c72df988281d6af48446300766fd3c Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 11 Jul 2024 15:10:16 -0400 Subject: [PATCH 238/272] fix: Remove check for Protobuf version (#474) * fix: Remove check for Protobuf version * restore unconditional behaviour * restore behaviour * restore comment * catch AttributeError --- packages/proto-plus/proto/marshal/compat.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/proto-plus/proto/marshal/compat.py b/packages/proto-plus/proto/marshal/compat.py index dc77f3ac067b..d1b7c29033d0 100644 --- a/packages/proto-plus/proto/marshal/compat.py +++ b/packages/proto-plus/proto/marshal/compat.py @@ -19,9 +19,6 @@ # 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. @@ -51,13 +48,12 @@ repeated_composite_types += (_message.RepeatedCompositeContainer,) repeated_scalar_types += (_message.RepeatedScalarContainer,) - # 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."]: + try: map_composite_types += (_message.MessageMapContainer,) + except AttributeError: + # The `MessageMapContainer` attribute is not available in Protobuf 5.x+ + pass + __all__ = ( "repeated_composite_types", From 9cfcbbf148cfba6bf1b2d30fdc786e87414b8dd7 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:35:53 -0400 Subject: [PATCH 239/272] chore(python): use python 3.10 for docs build (#468) * chore(python): use python 3.10 for docs build Source-Link: https://github.com/googleapis/synthtool/commit/9ae07858520bf035a3d5be569b5a65d960ee4392 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:52210e0e0559f5ea8c52be148b33504022e1faef4e95fbe4b32d68022af2fa7e * use python 3.10 for docs build --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- .../proto-plus/.kokoro/docker/docs/Dockerfile | 21 +++++---- .../.kokoro/docker/docs/requirements.txt | 40 ++++++++-------- packages/proto-plus/.kokoro/requirements.txt | 46 +++++++++---------- packages/proto-plus/noxfile.py | 2 +- 5 files changed, 60 insertions(+), 53 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 91d742b5b9fe..f30cb3775afc 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:d3de8a02819f65001effcbd3ea76ce97e9bcff035c7a89457f40f892c87c5b32 -# created: 2024-07-03T17:43:00.77142528Z + digest: sha256:52210e0e0559f5ea8c52be148b33504022e1faef4e95fbe4b32d68022af2fa7e +# created: 2024-07-08T19:25:35.862283192Z diff --git a/packages/proto-plus/.kokoro/docker/docs/Dockerfile b/packages/proto-plus/.kokoro/docker/docs/Dockerfile index a26ce61930f5..5205308b334d 100644 --- a/packages/proto-plus/.kokoro/docker/docs/Dockerfile +++ b/packages/proto-plus/.kokoro/docker/docs/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ubuntu:22.04 +from ubuntu:24.04 ENV DEBIAN_FRONTEND noninteractive @@ -40,7 +40,6 @@ RUN apt-get update \ libssl-dev \ libsqlite3-dev \ portaudio19-dev \ - python3-distutils \ redis-server \ software-properties-common \ ssh \ @@ -60,18 +59,22 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* \ && rm -f /var/cache/apt/archives/*.deb -###################### Install python 3.9.13 -# Download python 3.9.13 -RUN wget https://www.python.org/ftp/python/3.9.13/Python-3.9.13.tgz +###################### Install python 3.10.14 for docs/docfx session + +# Download python 3.10.14 +RUN wget https://www.python.org/ftp/python/3.10.14/Python-3.10.14.tgz # Extract files -RUN tar -xvf Python-3.9.13.tgz +RUN tar -xvf Python-3.10.14.tgz -# Install python 3.9.13 -RUN ./Python-3.9.13/configure --enable-optimizations +# Install python 3.10.14 +RUN ./Python-3.10.14/configure --enable-optimizations RUN make altinstall +RUN python3.10 -m venv /venv +ENV PATH /venv/bin:$PATH + ###################### Install pip RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ && python3 /tmp/get-pip.py \ @@ -84,4 +87,4 @@ RUN python3 -m pip COPY requirements.txt /requirements.txt RUN python3 -m pip install --require-hashes -r requirements.txt -CMD ["python3.8"] +CMD ["python3.10"] diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.txt b/packages/proto-plus/.kokoro/docker/docs/requirements.txt index 0e5d70f20f83..7129c7715594 100644 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.txt +++ b/packages/proto-plus/.kokoro/docker/docs/requirements.txt @@ -4,9 +4,9 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.2.3 \ - --hash=sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23 \ - --hash=sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c +argcomplete==3.4.0 \ + --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ + --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f # via nox colorlog==6.8.2 \ --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ @@ -16,23 +16,27 @@ distlib==0.3.8 \ --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 # via virtualenv -filelock==3.13.1 \ - --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ - --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c +filelock==3.15.4 \ + --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ + --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 # via virtualenv -nox==2024.3.2 \ - --hash=sha256:e53514173ac0b98dd47585096a55572fe504fecede58ced708979184d05440be \ - --hash=sha256:f521ae08a15adbf5e11f16cb34e8d0e6ea521e0b92868f684e91677deb974553 +nox==2024.4.15 \ + --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ + --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f # via -r requirements.in -packaging==24.0 \ - --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ - --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via nox -platformdirs==4.2.0 \ - --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \ - --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768 +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 # via virtualenv -virtualenv==20.25.1 \ - --hash=sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a \ - --hash=sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197 +tomli==2.0.1 \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f + # via nox +virtualenv==20.26.3 \ + --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ + --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 # via nox diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 35ece0e4d2e9..9622baf0ba38 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -20,9 +20,9 @@ cachetools==5.3.3 \ --hash=sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945 \ --hash=sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105 # via google-auth -certifi==2024.6.2 \ - --hash=sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516 \ - --hash=sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56 +certifi==2024.7.4 \ + --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ + --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 # via requests cffi==1.16.0 \ --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ @@ -371,23 +371,23 @@ more-itertools==10.3.0 \ # via # jaraco-classes # jaraco-functools -nh3==0.2.17 \ - --hash=sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a \ - --hash=sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911 \ - --hash=sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb \ - --hash=sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a \ - --hash=sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc \ - --hash=sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028 \ - --hash=sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9 \ - --hash=sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3 \ - --hash=sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351 \ - --hash=sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10 \ - --hash=sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71 \ - --hash=sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f \ - --hash=sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b \ - --hash=sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a \ - --hash=sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062 \ - --hash=sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a +nh3==0.2.18 \ + --hash=sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164 \ + --hash=sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86 \ + --hash=sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b \ + --hash=sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad \ + --hash=sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204 \ + --hash=sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a \ + --hash=sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200 \ + --hash=sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189 \ + --hash=sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f \ + --hash=sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811 \ + --hash=sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844 \ + --hash=sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4 \ + --hash=sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be \ + --hash=sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50 \ + --hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \ + --hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe # via readme-renderer nox==2024.4.15 \ --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ @@ -460,9 +460,9 @@ python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via gcp-releasetool -readme-renderer==43.0 \ - --hash=sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311 \ - --hash=sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9 +readme-renderer==44.0 \ + --hash=sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 \ + --hash=sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1 # via twine requests==2.32.3 \ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 19c80c9525ad..fc9d93666c60 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -129,7 +129,7 @@ def prerelease_deps(session, implementation): ) -@nox.session(python="3.9") +@nox.session(python="3.10") def docs(session): """Build the docs.""" From fb9ca9996e4ba248989391b8bb8130f9c2757828 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Fri, 12 Jul 2024 12:27:48 -0400 Subject: [PATCH 240/272] docs: fix typos in proto/message.py (#463) --- packages/proto-plus/proto/message.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 97e745101ef1..08d71658ff4f 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -481,7 +481,7 @@ def to_json( `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. + if both arguments are explicitly 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. @@ -497,7 +497,7 @@ def to_json( 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. + if both arguments are explicitly set. Returns: str: The json string representation of the protocol buffer. """ @@ -574,14 +574,14 @@ def to_dict( `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. + if both arguments are explicitly 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. + must match `including_default_value_fields`, if both arguments are explicitly set. Returns: dict: A representation of the protocol buffer using pythonic data structures. From ed51572f67a44793274ec93b4d4f23130c5851d1 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Fri, 12 Jul 2024 14:16:23 -0400 Subject: [PATCH 241/272] fix: fix conda compatibility issue (#475) * fix: fix conda compatibility issue * add comment * typo --- packages/proto-plus/proto/marshal/compat.py | 37 +++++++++++---------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/proto-plus/proto/marshal/compat.py b/packages/proto-plus/proto/marshal/compat.py index d1b7c29033d0..b393acf5a447 100644 --- a/packages/proto-plus/proto/marshal/compat.py +++ b/packages/proto-plus/proto/marshal/compat.py @@ -20,18 +20,19 @@ from google.protobuf.internal import containers -# Import protobuf 4.xx first and fallback to earlier version -# if not present. +# Import all message types to ensure that pyext types are recognized +# when upb types exist. Conda's protobuf defaults to pyext despite upb existing. +# See https://github.com/googleapis/proto-plus-python/issues/470 try: - from google._upb import _message + from google._upb import _message as _message_upb except ImportError: - _message = None + _message_upb = None + +try: + from google.protobuf.pyext import _message as _message_pyext +except ImportError: + _message_pyext = None -if not _message: - try: - from google.protobuf.pyext import _message - except ImportError: - _message = None repeated_composite_types = (containers.RepeatedCompositeFieldContainer,) repeated_scalar_types = (containers.RepeatedScalarFieldContainer,) @@ -44,16 +45,16 @@ # 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,) - - try: - map_composite_types += (_message.MessageMapContainer,) - except AttributeError: - # The `MessageMapContainer` attribute is not available in Protobuf 5.x+ - pass +for message in [_message_upb, _message_pyext]: + if message: + repeated_composite_types += (message.RepeatedCompositeContainer,) + repeated_scalar_types += (message.RepeatedScalarContainer,) + try: + map_composite_types += (message.MessageMapContainer,) + except AttributeError: + # The `MessageMapContainer` attribute is not available in Protobuf 5.x+ + pass __all__ = ( "repeated_composite_types", From 631c3347b389b4ca3f28fc6053e0db099b066180 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 29 Jul 2024 13:10:12 -0400 Subject: [PATCH 242/272] fix: fix issue with equality comparison of repeated field with None (#477) * fix: fix issue with equality comparison of repeated field with None * style --- .../proto-plus/proto/marshal/collections/repeated.py | 9 +++++++-- .../proto-plus/tests/test_fields_repeated_composite.py | 1 + packages/proto-plus/tests/test_fields_repeated_scalar.py | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/marshal/collections/repeated.py b/packages/proto-plus/proto/marshal/collections/repeated.py index 29bacff6d6c3..a6560411cd71 100644 --- a/packages/proto-plus/proto/marshal/collections/repeated.py +++ b/packages/proto-plus/proto/marshal/collections/repeated.py @@ -14,6 +14,7 @@ import collections import copy +from typing import Iterable from proto.utils import cached_property @@ -48,7 +49,7 @@ def __delitem__(self, key): def __eq__(self, other): if hasattr(other, "pb"): return tuple(self.pb) == tuple(other.pb) - return tuple(self.pb) == tuple(other) + return tuple(self.pb) == tuple(other) if isinstance(other, Iterable) else False def __getitem__(self, key): """Return the given item.""" @@ -119,7 +120,11 @@ def _pb_type(self): def __eq__(self, other): if super().__eq__(other): return True - return tuple([i for i in self]) == tuple(other) + return ( + tuple([i for i in self]) == tuple(other) + if isinstance(other, Iterable) + else False + ) def __getitem__(self, key): return self._marshal.to_python(self._pb_type, self.pb[key]) diff --git a/packages/proto-plus/tests/test_fields_repeated_composite.py b/packages/proto-plus/tests/test_fields_repeated_composite.py index db6be27baef3..7c9f73e4415e 100644 --- a/packages/proto-plus/tests/test_fields_repeated_composite.py +++ b/packages/proto-plus/tests/test_fields_repeated_composite.py @@ -47,6 +47,7 @@ class Baz(proto.Message): baz = Baz(foos=[Foo(bar=42)]) assert baz.foos == baz.foos + assert baz.foos != None def test_repeated_composite_init_struct(): diff --git a/packages/proto-plus/tests/test_fields_repeated_scalar.py b/packages/proto-plus/tests/test_fields_repeated_scalar.py index e07cf1306f0e..d63831322e11 100644 --- a/packages/proto-plus/tests/test_fields_repeated_scalar.py +++ b/packages/proto-plus/tests/test_fields_repeated_scalar.py @@ -72,6 +72,7 @@ class Foo(proto.Message): foo = Foo(bar=[1, 1, 2, 3, 5, 8, 13]) assert foo.bar == copy.copy(foo.bar) assert foo.bar != [1, 2, 4, 8, 16] + assert foo.bar != None def test_repeated_scalar_del(): From bbb91bbb2f5e5de54b134503436258e1753bced3 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 30 Jul 2024 13:34:20 -0400 Subject: [PATCH 243/272] fix: construct messages with nested struct (#479) * fix: construct messages with nested struct * fix lint issues * some cleanup * remove code for handling underscore keys * update commit message * fix style --------- Co-authored-by: Anthonios Partheniou --- .../proto-plus/proto/marshal/rules/message.py | 10 ++++-- packages/proto-plus/proto/message.py | 31 +------------------ .../tests/test_marshal_types_struct.py | 23 ++++++++++++++ 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/packages/proto-plus/proto/marshal/rules/message.py b/packages/proto-plus/proto/marshal/rules/message.py index c865b99d99d9..479a2d952776 100644 --- a/packages/proto-plus/proto/marshal/rules/message.py +++ b/packages/proto-plus/proto/marshal/rules/message.py @@ -34,10 +34,14 @@ def to_proto(self, value): try: # Try the fast path first. return self._descriptor(**value) - except TypeError as ex: - # If we have a type error, + except (TypeError, ValueError) as ex: + # If we have a TypeError or Valueerror, # try the slow path in case the error - # was an int64/string issue + # was: + # - an int64/string issue. + # - a missing key issue in case a key only exists with a `_` suffix. + # See related issue: https://github.com/googleapis/python-api-core/issues/227. + # - a missing key issue due to nested struct. See: b/321905145. return self._wrapper(value)._pb return value diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 08d71658ff4f..989c1cd32a86 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -725,36 +725,7 @@ def __init__( "Unknown field for {}: {}".format(self.__class__.__name__, key) ) - try: - pb_value = marshal.to_proto(pb_type, value) - except ValueError: - # Underscores may be appended to field names - # that collide with python or proto-plus keywords. - # In case a key only exists with a `_` suffix, coerce the key - # to include the `_` suffix. It's not possible to - # natively define the same field with a trailing underscore in protobuf. - # See related issue - # https://github.com/googleapis/python-api-core/issues/227 - if isinstance(value, dict): - if _upb: - # In UPB, pb_type is MessageMeta which doesn't expose attrs like it used to in Python/CPP. - keys_to_update = [ - item - for item in value - if item not in pb_type.DESCRIPTOR.fields_by_name - and f"{item}_" in pb_type.DESCRIPTOR.fields_by_name - ] - else: - keys_to_update = [ - item - for item in value - if not hasattr(pb_type, item) - and hasattr(pb_type, f"{item}_") - ] - for item in keys_to_update: - value[f"{item}_"] = value.pop(item) - - pb_value = marshal.to_proto(pb_type, value) + pb_value = marshal.to_proto(pb_type, value) if pb_value is not None: params[key] = pb_value diff --git a/packages/proto-plus/tests/test_marshal_types_struct.py b/packages/proto-plus/tests/test_marshal_types_struct.py index 914730c5745c..8ca2cde8a3c1 100644 --- a/packages/proto-plus/tests/test_marshal_types_struct.py +++ b/packages/proto-plus/tests/test_marshal_types_struct.py @@ -243,3 +243,26 @@ class Foo(proto.Message): detached["bacon"] = True foo.value = detached assert foo.value == {"foo": "bar", "bacon": True} + + +def test_struct_nested(): + class Foo(proto.Message): + struct_field: struct_pb2.Struct = proto.Field( + proto.MESSAGE, + number=1, + message=struct_pb2.Struct, + ) + + class Bar(proto.Message): + foo_field: Foo = proto.Field( + proto.MESSAGE, + number=1, + message=Foo, + ) + + foo = Foo({"struct_field": {"foo": "bagel"}}) + assert foo.struct_field == {"foo": "bagel"} + + bar = Bar({"foo_field": {"struct_field": {"foo": "cheese"}}}) + assert bar.foo_field == Foo({"struct_field": {"foo": "cheese"}}) + assert bar.foo_field.struct_field == {"foo": "cheese"} From 60e10d5a216a243a0dcee0b46feccbd1f6b69de7 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:55:54 -0400 Subject: [PATCH 244/272] chore(python): fix docs build (#480) Source-Link: https://github.com/googleapis/synthtool/commit/bef813d194de29ddf3576eda60148b6b3dcc93d9 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:94bb690db96e6242b2567a4860a94d48fa48696d092e51b0884a1a2c0a79a407 Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- .../proto-plus/.kokoro/docker/docs/Dockerfile | 9 ++++----- packages/proto-plus/.kokoro/publish-docs.sh | 20 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index f30cb3775afc..6d064ddb9b06 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:52210e0e0559f5ea8c52be148b33504022e1faef4e95fbe4b32d68022af2fa7e -# created: 2024-07-08T19:25:35.862283192Z + digest: sha256:94bb690db96e6242b2567a4860a94d48fa48696d092e51b0884a1a2c0a79a407 +# created: 2024-07-31T14:52:44.926548819Z diff --git a/packages/proto-plus/.kokoro/docker/docs/Dockerfile b/packages/proto-plus/.kokoro/docker/docs/Dockerfile index 5205308b334d..e5410e296bd8 100644 --- a/packages/proto-plus/.kokoro/docker/docs/Dockerfile +++ b/packages/proto-plus/.kokoro/docker/docs/Dockerfile @@ -72,19 +72,18 @@ RUN tar -xvf Python-3.10.14.tgz RUN ./Python-3.10.14/configure --enable-optimizations RUN make altinstall -RUN python3.10 -m venv /venv -ENV PATH /venv/bin:$PATH +ENV PATH /usr/local/bin/python3.10:$PATH ###################### Install pip RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ - && python3 /tmp/get-pip.py \ + && python3.10 /tmp/get-pip.py \ && rm /tmp/get-pip.py # Test pip -RUN python3 -m pip +RUN python3.10 -m pip # Install build requirements COPY requirements.txt /requirements.txt -RUN python3 -m pip install --require-hashes -r requirements.txt +RUN python3.10 -m pip install --require-hashes -r requirements.txt CMD ["python3.10"] diff --git a/packages/proto-plus/.kokoro/publish-docs.sh b/packages/proto-plus/.kokoro/publish-docs.sh index 38f083f05aa0..233205d580e9 100755 --- a/packages/proto-plus/.kokoro/publish-docs.sh +++ b/packages/proto-plus/.kokoro/publish-docs.sh @@ -21,18 +21,18 @@ export PYTHONUNBUFFERED=1 export PATH="${HOME}/.local/bin:${PATH}" # Install nox -python3 -m pip install --require-hashes -r .kokoro/requirements.txt -python3 -m nox --version +python3.10 -m pip install --require-hashes -r .kokoro/requirements.txt +python3.10 -m nox --version # build docs nox -s docs # create metadata -python3 -m docuploader create-metadata \ +python3.10 -m docuploader create-metadata \ --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3 setup.py --version) \ + --version=$(python3.10 setup.py --version) \ --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3 setup.py --name) \ + --distribution-name=$(python3.10 setup.py --name) \ --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) @@ -40,18 +40,18 @@ python3 -m docuploader create-metadata \ cat docs.metadata # upload docs -python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" +python3.10 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" # docfx yaml files nox -s docfx # create metadata. -python3 -m docuploader create-metadata \ +python3.10 -m docuploader create-metadata \ --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3 setup.py --version) \ + --version=$(python3.10 setup.py --version) \ --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3 setup.py --name) \ + --distribution-name=$(python3.10 setup.py --name) \ --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) @@ -59,4 +59,4 @@ python3 -m docuploader create-metadata \ cat docs.metadata # upload docs -python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" +python3.10 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" From 041033aad99a973caf1c9bc42839e31cdc4021bb Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:11:53 -0400 Subject: [PATCH 245/272] build(python): release script update (#487) * build(python): release script update Source-Link: https://github.com/googleapis/synthtool/commit/71a72973dddbc66ea64073b53eda49f0d22e0942 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e8dcfd7cbfd8beac3a3ff8d3f3185287ea0625d859168cc80faccfc9a7a00455 * see https://github.com/googleapis/synthtool/pull/2008 --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.github/workflows/tests.yml | 1 + packages/proto-plus/.kokoro/release.sh | 2 +- packages/proto-plus/.kokoro/release/common.cfg | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 6d064ddb9b06..597e0c3261ca 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:94bb690db96e6242b2567a4860a94d48fa48696d092e51b0884a1a2c0a79a407 -# created: 2024-07-31T14:52:44.926548819Z + digest: sha256:e8dcfd7cbfd8beac3a3ff8d3f3185287ea0625d859168cc80faccfc9a7a00455 +# created: 2024-09-16T21:04:09.091105552Z diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index edbc2141b572..83d183105fb7 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -82,6 +82,7 @@ jobs: with: name: coverage-artifact-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} path: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} + include-hidden-files: true prerelease: runs-on: ubuntu-22.04 strategy: diff --git a/packages/proto-plus/.kokoro/release.sh b/packages/proto-plus/.kokoro/release.sh index 2083f94788b4..50d3a6275e5b 100755 --- a/packages/proto-plus/.kokoro/release.sh +++ b/packages/proto-plus/.kokoro/release.sh @@ -23,7 +23,7 @@ python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source / export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-2") cd github/proto-plus-python python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/proto-plus/.kokoro/release/common.cfg b/packages/proto-plus/.kokoro/release/common.cfg index e6c5929796b3..0a915e8c83dd 100644 --- a/packages/proto-plus/.kokoro/release/common.cfg +++ b/packages/proto-plus/.kokoro/release/common.cfg @@ -28,7 +28,7 @@ before_action { fetch_keystore { keystore_resource { keystore_config_id: 73713 - keyname: "google-cloud-pypi-token-keystore-1" + keyname: "google-cloud-pypi-token-keystore-2" } } } From ccbfd451ea47d0b26ef1957f73490a06c1989ded Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 15 Oct 2024 12:47:49 -0400 Subject: [PATCH 246/272] fix: fix 'Couldn't build proto file' when using Python 3.13 (#492) * fix: fix 'Couldn't build proto file' when using Python 3.13 * fix: fix 'Couldn't build proto file' when using Python 3.13 * add comment --- packages/proto-plus/proto/enums.py | 4 +++- packages/proto-plus/tests/clam.py | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 6207ad7d1353..4073c2a3c677 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -73,8 +73,10 @@ def __new__(mcls, name, bases, attrs): ( descriptor_pb2.EnumValueDescriptorProto(name=name, number=number) # Minor hack to get all the enum variants out. + # Use the `_member_names` property to get only the enum members + # See https://github.com/googleapis/proto-plus-python/issues/490 for name, number in attrs.items() - if isinstance(number, int) + if name in attrs._member_names and isinstance(number, int) ), key=lambda v: v.number, ), diff --git a/packages/proto-plus/tests/clam.py b/packages/proto-plus/tests/clam.py index 4946ebe16a91..2f9dde13bd73 100644 --- a/packages/proto-plus/tests/clam.py +++ b/packages/proto-plus/tests/clam.py @@ -19,6 +19,7 @@ manifest={ "Clam", "Species", + "Color", }, ) @@ -30,6 +31,14 @@ class Species(proto.Enum): GIGAS = 3 +class Color(proto.Enum): + COLOR_UNKNOWN = 0 + BLUE = 1 + ORANGE = 2 + GREEN = 3 + + class Clam(proto.Message): species = proto.Field(proto.ENUM, number=1, enum="Species") mass_kg = proto.Field(proto.DOUBLE, number=2) + color = proto.Field(proto.ENUM, number=3, enum="Color") From 66e8494cfbbf6bcbc0e9b2a65cc500fe47b7f987 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 15 Oct 2024 14:56:05 -0400 Subject: [PATCH 247/272] feat: Add support for Python 3.13 (#493) * feat: Add support for Python 3.13 * update python version for prerelease_deps --- packages/proto-plus/.github/sync-repo-settings.yaml | 6 ++++-- packages/proto-plus/.github/workflows/tests.yml | 2 +- packages/proto-plus/noxfile.py | 2 +- packages/proto-plus/setup.py | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index d85818a50d92..d66f708a6c1c 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -23,8 +23,10 @@ branchProtectionRules: - 'unit (3.11, upb)' - 'unit (3.12, python)' - 'unit (3.12, upb)' - - 'prerelease (3.12, python)' - - 'prerelease (3.12, upb)' + - 'unit (3.13, python)' + - 'unit (3.13, upb)' + - 'prerelease (3.13, python)' + - 'prerelease (3.13, upb)' - cover - OwlBot Post Processor - 'cla/google' diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 83d183105fb7..de787dfd365f 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -87,7 +87,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python: ['3.12'] + python: ['3.13'] variant: ['python', 'upb'] steps: - uses: actions/checkout@v4 diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index fc9d93666c60..1c16bc51843e 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -77,7 +77,7 @@ def unit(session, implementation): # 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.session(python=PYTHON_VERSIONS[-1]) @nox.parametrize("implementation", ["python", "upb"]) def prerelease_deps(session, implementation): """Run the unit test suite against pre-release versions of dependencies.""" diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index e3a548921974..d8a430f21b53 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -62,6 +62,7 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", ], From 81154498a9b40967320ef9e9f062ee18289fd5c9 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:11:14 -0400 Subject: [PATCH 248/272] chore(main): release 1.25.0 (#473) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 22 ++++++++++++++++++++++ packages/proto-plus/proto/version.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 2171e424265f..238107e8df5e 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [1.25.0](https://github.com/googleapis/proto-plus-python/compare/v1.24.0...v1.25.0) (2024-10-15) + + +### Features + +* Add support for Python 3.13 ([#493](https://github.com/googleapis/proto-plus-python/issues/493)) ([e9643a1](https://github.com/googleapis/proto-plus-python/commit/e9643a1f6135267d4389c77722120e6c98342a74)) + + +### Bug Fixes + +* Construct messages with nested struct ([#479](https://github.com/googleapis/proto-plus-python/issues/479)) ([aa4aa61](https://github.com/googleapis/proto-plus-python/commit/aa4aa61b8c7ac0cc34d2d5797999bb434de88737)) +* Fix 'Couldn't build proto file' when using Python 3.13 ([#492](https://github.com/googleapis/proto-plus-python/issues/492)) ([a48c39f](https://github.com/googleapis/proto-plus-python/commit/a48c39ff2212261bc932d10132086a6c55be22e9)) +* Fix conda compatibility issue ([#475](https://github.com/googleapis/proto-plus-python/issues/475)) ([e2f9c9d](https://github.com/googleapis/proto-plus-python/commit/e2f9c9d1c87230d0e8d9ccacdfd0872792d54f1b)) +* Fix issue with equality comparison of repeated field with None ([#477](https://github.com/googleapis/proto-plus-python/issues/477)) ([3476348](https://github.com/googleapis/proto-plus-python/commit/3476348c995af2ce5dfcbcc688e9ddf98fa36360)) +* Remove check for Protobuf version ([#474](https://github.com/googleapis/proto-plus-python/issues/474)) ([a1748a3](https://github.com/googleapis/proto-plus-python/commit/a1748a315b6b50128b0d9927b2fee353ec55975f)) + + +### Documentation + +* Fix typos in proto/message.py ([#463](https://github.com/googleapis/proto-plus-python/issues/463)) ([4d8ee65](https://github.com/googleapis/proto-plus-python/commit/4d8ee656e008ec2b22f347e5da539b6285ec4b1b)) +* Update message.py spelling error `paylod` → `payload` ([e59fc9a](https://github.com/googleapis/proto-plus-python/commit/e59fc9a4f8dd9fcb0804b77347662ae29d1a31a1)) + ## [1.24.0](https://github.com/googleapis/proto-plus-python/compare/v1.23.0...v1.24.0) (2024-06-11) diff --git a/packages/proto-plus/proto/version.py b/packages/proto-plus/proto/version.py index dbdc76e064e4..ab2f7680f172 100644 --- a/packages/proto-plus/proto/version.py +++ b/packages/proto-plus/proto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.24.0" +__version__ = "1.25.0" From a3120934a1d73b061447317717026dab931194af Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 22 Oct 2024 16:54:51 -0400 Subject: [PATCH 249/272] chore: update templated files (#498) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update templated files * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- .../.kokoro/docker/docs/requirements.txt | 42 +- packages/proto-plus/.kokoro/release.sh | 2 +- .../proto-plus/.kokoro/release/common.cfg | 2 +- packages/proto-plus/.kokoro/requirements.txt | 610 +++++++++--------- 5 files changed, 316 insertions(+), 344 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 597e0c3261ca..db2377a5652a 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:e8dcfd7cbfd8beac3a3ff8d3f3185287ea0625d859168cc80faccfc9a7a00455 -# created: 2024-09-16T21:04:09.091105552Z + digest: sha256:56cd3192d58a72bcd10b031ff2917d75b6b3c2a39a2571c080f1d6c519d40330 +# created: 2024-10-22 diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.txt b/packages/proto-plus/.kokoro/docker/docs/requirements.txt index 7129c7715594..66eacc82f041 100644 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.txt +++ b/packages/proto-plus/.kokoro/docker/docs/requirements.txt @@ -4,39 +4,39 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.4.0 \ - --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ - --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f +argcomplete==3.5.1 \ + --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ + --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 # via nox colorlog==6.8.2 \ --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 # via nox -distlib==0.3.8 \ - --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ - --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 +distlib==0.3.9 \ + --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ + --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 # via virtualenv -filelock==3.15.4 \ - --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ - --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 +filelock==3.16.1 \ + --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ + --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 # via virtualenv -nox==2024.4.15 \ - --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ - --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f +nox==2024.10.9 \ + --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ + --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 # via -r requirements.in packaging==24.1 \ --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via nox -platformdirs==4.2.2 \ - --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ - --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via virtualenv -tomli==2.0.1 \ - --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ - --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f +tomli==2.0.2 \ + --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ + --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed # via nox -virtualenv==20.26.3 \ - --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ - --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 +virtualenv==20.26.6 \ + --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ + --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 # via nox diff --git a/packages/proto-plus/.kokoro/release.sh b/packages/proto-plus/.kokoro/release.sh index 50d3a6275e5b..74a2e4060323 100755 --- a/packages/proto-plus/.kokoro/release.sh +++ b/packages/proto-plus/.kokoro/release.sh @@ -23,7 +23,7 @@ python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source / export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-2") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-3") cd github/proto-plus-python python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/proto-plus/.kokoro/release/common.cfg b/packages/proto-plus/.kokoro/release/common.cfg index 0a915e8c83dd..e244d5ece4aa 100644 --- a/packages/proto-plus/.kokoro/release/common.cfg +++ b/packages/proto-plus/.kokoro/release/common.cfg @@ -28,7 +28,7 @@ before_action { fetch_keystore { keystore_resource { keystore_config_id: 73713 - keyname: "google-cloud-pypi-token-keystore-2" + keyname: "google-cloud-pypi-token-keystore-3" } } } diff --git a/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt index 9622baf0ba38..006d8ef931bf 100644 --- a/packages/proto-plus/.kokoro/requirements.txt +++ b/packages/proto-plus/.kokoro/requirements.txt @@ -4,79 +4,94 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.4.0 \ - --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ - --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f +argcomplete==3.5.1 \ + --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ + --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 # via nox -attrs==23.2.0 \ - --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ - --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 +attrs==24.2.0 \ + --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \ + --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2 # via gcp-releasetool backports-tarfile==1.2.0 \ --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 # via jaraco-context -cachetools==5.3.3 \ - --hash=sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945 \ - --hash=sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105 +cachetools==5.5.0 \ + --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ + --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a # via google-auth -certifi==2024.7.4 \ - --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ - --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 +certifi==2024.8.30 \ + --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \ + --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9 # via requests -cffi==1.16.0 \ - --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ - --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ - --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ - --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ - --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ - --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ - --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ - --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ - --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ - --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ - --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ - --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ - --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ - --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ - --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ - --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ - --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ - --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ - --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ - --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ - --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ - --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ - --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ - --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ - --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ - --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ - --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ - --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ - --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ - --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ - --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ - --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ - --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ - --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ - --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ - --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ - --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ - --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ - --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ - --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ - --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ - --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ - --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ - --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ - --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ - --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ - --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ - --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ - --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ - --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ - --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ - --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 +cffi==1.17.1 \ + --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ + --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ + --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ + --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ + --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ + --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ + --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ + --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ + --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ + --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ + --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ + --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ + --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ + --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ + --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ + --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ + --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ + --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ + --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ + --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ + --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ + --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ + --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ + --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ + --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ + --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ + --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ + --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ + --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ + --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ + --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ + --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ + --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ + --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ + --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ + --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ + --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ + --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ + --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ + --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ + --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ + --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ + --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ + --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ + --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ + --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # via cryptography charset-normalizer==2.1.1 \ --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ @@ -97,72 +112,67 @@ colorlog==6.8.2 \ # via # gcp-docuploader # nox -cryptography==42.0.8 \ - --hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \ - --hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \ - --hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \ - --hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \ - --hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \ - --hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \ - --hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \ - --hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \ - --hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \ - --hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \ - --hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \ - --hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \ - --hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \ - --hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \ - --hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \ - --hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \ - --hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \ - --hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \ - --hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \ - --hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \ - --hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \ - --hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \ - --hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \ - --hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \ - --hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \ - --hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \ - --hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \ - --hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \ - --hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \ - --hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \ - --hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \ - --hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e +cryptography==43.0.1 \ + --hash=sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494 \ + --hash=sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806 \ + --hash=sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d \ + --hash=sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062 \ + --hash=sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2 \ + --hash=sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4 \ + --hash=sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1 \ + --hash=sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85 \ + --hash=sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84 \ + --hash=sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042 \ + --hash=sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d \ + --hash=sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962 \ + --hash=sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2 \ + --hash=sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa \ + --hash=sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d \ + --hash=sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365 \ + --hash=sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96 \ + --hash=sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47 \ + --hash=sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d \ + --hash=sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d \ + --hash=sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c \ + --hash=sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb \ + --hash=sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277 \ + --hash=sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172 \ + --hash=sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034 \ + --hash=sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a \ + --hash=sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289 # via # -r requirements.in # gcp-releasetool # secretstorage -distlib==0.3.8 \ - --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ - --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 +distlib==0.3.9 \ + --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ + --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 # via virtualenv docutils==0.21.2 \ --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 # via readme-renderer -filelock==3.15.4 \ - --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ - --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 +filelock==3.16.1 \ + --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ + --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 # via virtualenv gcp-docuploader==0.6.5 \ --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==2.0.1 \ - --hash=sha256:34314a910c08e8911d9c965bd44f8f2185c4f556e737d719c33a41f6a610de96 \ - --hash=sha256:b0d5863c6a070702b10883d37c4bdfd74bf930fe417f36c0c965d3b7c779ae62 +gcp-releasetool==2.1.1 \ + --hash=sha256:25639269f4eae510094f9dbed9894977e1966933211eb155a451deebc3fc0b30 \ + --hash=sha256:845f4ded3d9bfe8cc7fdaad789e83f4ea014affa77785259a7ddac4b243e099e # via -r requirements.in -google-api-core==2.19.1 \ - --hash=sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125 \ - --hash=sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd +google-api-core==2.21.0 \ + --hash=sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81 \ + --hash=sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d # via # google-cloud-core # google-cloud-storage -google-auth==2.31.0 \ - --hash=sha256:042c4702efa9f7d3c48d3a69341c209381b125faa6dbf3ebe56bc7e40ae05c23 \ - --hash=sha256:87805c36970047247c8afe614d4e3af8eceafc1ebba0c679fe75ddd1d575e871 +google-auth==2.35.0 \ + --hash=sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f \ + --hash=sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a # via # gcp-releasetool # google-api-core @@ -172,97 +182,56 @@ google-cloud-core==2.4.1 \ --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 # via google-cloud-storage -google-cloud-storage==2.17.0 \ - --hash=sha256:49378abff54ef656b52dca5ef0f2eba9aa83dc2b2c72c78714b03a1a95fe9388 \ - --hash=sha256:5b393bc766b7a3bc6f5407b9e665b2450d36282614b7945e570b3480a456d1e1 +google-cloud-storage==2.18.2 \ + --hash=sha256:97a4d45c368b7d401ed48c4fdfe86e1e1cb96401c9e199e419d289e2c0370166 \ + --hash=sha256:aaf7acd70cdad9f274d29332673fcab98708d0e1f4dceb5a5356aaef06af4d99 # via gcp-docuploader -google-crc32c==1.5.0 \ - --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ - --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ - --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ - --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ - --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ - --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ - --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ - --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ - --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ - --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ - --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ - --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ - --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ - --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ - --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ - --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ - --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ - --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ - --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ - --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ - --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ - --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ - --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ - --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ - --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ - --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ - --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ - --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ - --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ - --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ - --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ - --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ - --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ - --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ - --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ - --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ - --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ - --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ - --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ - --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ - --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ - --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ - --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ - --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ - --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ - --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ - --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ - --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ - --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ - --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ - --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ - --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ - --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ - --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ - --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ - --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ - --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ - --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ - --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ - --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ - --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ - --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ - --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ - --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ - --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ - --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ - --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ - --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 +google-crc32c==1.6.0 \ + --hash=sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24 \ + --hash=sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d \ + --hash=sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e \ + --hash=sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57 \ + --hash=sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2 \ + --hash=sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8 \ + --hash=sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc \ + --hash=sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42 \ + --hash=sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f \ + --hash=sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa \ + --hash=sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b \ + --hash=sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc \ + --hash=sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760 \ + --hash=sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d \ + --hash=sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7 \ + --hash=sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d \ + --hash=sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0 \ + --hash=sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3 \ + --hash=sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3 \ + --hash=sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00 \ + --hash=sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871 \ + --hash=sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c \ + --hash=sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9 \ + --hash=sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205 \ + --hash=sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc \ + --hash=sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d \ + --hash=sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4 # via # google-cloud-storage # google-resumable-media -google-resumable-media==2.7.1 \ - --hash=sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c \ - --hash=sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33 +google-resumable-media==2.7.2 \ + --hash=sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa \ + --hash=sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0 # via google-cloud-storage -googleapis-common-protos==1.63.2 \ - --hash=sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945 \ - --hash=sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87 +googleapis-common-protos==1.65.0 \ + --hash=sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63 \ + --hash=sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0 # via google-api-core -idna==3.7 \ - --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ - --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 # via requests -importlib-metadata==8.0.0 \ - --hash=sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f \ - --hash=sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812 +importlib-metadata==8.5.0 \ + --hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \ + --hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7 # via # -r requirements.in # keyring @@ -271,13 +240,13 @@ jaraco-classes==3.4.0 \ --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 # via keyring -jaraco-context==5.3.0 \ - --hash=sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266 \ - --hash=sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2 +jaraco-context==6.0.1 \ + --hash=sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3 \ + --hash=sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4 # via keyring -jaraco-functools==4.0.1 \ - --hash=sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664 \ - --hash=sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8 +jaraco-functools==4.1.0 \ + --hash=sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d \ + --hash=sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -289,9 +258,9 @@ jinja2==3.1.4 \ --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via gcp-releasetool -keyring==25.2.1 \ - --hash=sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50 \ - --hash=sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b +keyring==25.4.1 \ + --hash=sha256:5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf \ + --hash=sha256:b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b # via # gcp-releasetool # twine @@ -299,75 +268,76 @@ markdown-it-py==3.0.0 \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb # via rich -markupsafe==2.1.5 \ - --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ - --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ - --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ - --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ - --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ - --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ - --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ - --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ - --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ - --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ - --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ - --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ - --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ - --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ - --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ - --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ - --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ - --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ - --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ - --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ - --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ - --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ - --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ - --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ - --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ - --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ - --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ - --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ - --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ - --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ - --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ - --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ - --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ - --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ - --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ - --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ - --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ - --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ - --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ - --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ - --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ - --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ - --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ - --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ - --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ - --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ - --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ - --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ - --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ - --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ - --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ - --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ - --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ - --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ - --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ - --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ - --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ - --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ - --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ - --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 +markupsafe==3.0.1 \ + --hash=sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396 \ + --hash=sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38 \ + --hash=sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a \ + --hash=sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8 \ + --hash=sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b \ + --hash=sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad \ + --hash=sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a \ + --hash=sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a \ + --hash=sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da \ + --hash=sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6 \ + --hash=sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8 \ + --hash=sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344 \ + --hash=sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a \ + --hash=sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8 \ + --hash=sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5 \ + --hash=sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7 \ + --hash=sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170 \ + --hash=sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132 \ + --hash=sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9 \ + --hash=sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd \ + --hash=sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9 \ + --hash=sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346 \ + --hash=sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc \ + --hash=sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589 \ + --hash=sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5 \ + --hash=sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915 \ + --hash=sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295 \ + --hash=sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453 \ + --hash=sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea \ + --hash=sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b \ + --hash=sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d \ + --hash=sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b \ + --hash=sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4 \ + --hash=sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b \ + --hash=sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7 \ + --hash=sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf \ + --hash=sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f \ + --hash=sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91 \ + --hash=sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd \ + --hash=sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50 \ + --hash=sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b \ + --hash=sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583 \ + --hash=sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a \ + --hash=sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984 \ + --hash=sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c \ + --hash=sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c \ + --hash=sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25 \ + --hash=sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa \ + --hash=sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4 \ + --hash=sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3 \ + --hash=sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97 \ + --hash=sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1 \ + --hash=sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd \ + --hash=sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772 \ + --hash=sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a \ + --hash=sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729 \ + --hash=sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca \ + --hash=sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6 \ + --hash=sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635 \ + --hash=sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b \ + --hash=sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f # via jinja2 mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py -more-itertools==10.3.0 \ - --hash=sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463 \ - --hash=sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320 +more-itertools==10.5.0 \ + --hash=sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef \ + --hash=sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6 # via # jaraco-classes # jaraco-functools @@ -389,9 +359,9 @@ nh3==0.2.18 \ --hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \ --hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe # via readme-renderer -nox==2024.4.15 \ - --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ - --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f +nox==2024.10.9 \ + --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ + --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 # via -r requirements.in packaging==24.1 \ --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ @@ -403,41 +373,41 @@ pkginfo==1.10.0 \ --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 # via twine -platformdirs==4.2.2 \ - --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ - --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via virtualenv proto-plus==1.24.0 \ --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 # via google-api-core -protobuf==5.27.2 \ - --hash=sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505 \ - --hash=sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b \ - --hash=sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38 \ - --hash=sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863 \ - --hash=sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470 \ - --hash=sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6 \ - --hash=sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce \ - --hash=sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca \ - --hash=sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5 \ - --hash=sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e \ - --hash=sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714 +protobuf==5.28.2 \ + --hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \ + --hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \ + --hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \ + --hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \ + --hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \ + --hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \ + --hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \ + --hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \ + --hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \ + --hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \ + --hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d # via # gcp-docuploader # gcp-releasetool # google-api-core # googleapis-common-protos # proto-plus -pyasn1==0.6.0 \ - --hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \ - --hash=sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473 +pyasn1==0.6.1 \ + --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ + --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 # via # pyasn1-modules # rsa -pyasn1-modules==0.4.0 \ - --hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \ - --hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b +pyasn1-modules==0.4.1 \ + --hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \ + --hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c # via google-auth pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ @@ -449,9 +419,9 @@ pygments==2.18.0 \ # via # readme-renderer # rich -pyjwt==2.8.0 \ - --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ - --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 +pyjwt==2.9.0 \ + --hash=sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850 \ + --hash=sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c # via gcp-releasetool pyperclip==1.9.0 \ --hash=sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310 @@ -481,9 +451,9 @@ rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==13.7.1 \ - --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ - --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 +rich==13.9.2 \ + --hash=sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c \ + --hash=sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1 # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ @@ -499,9 +469,9 @@ six==1.16.0 \ # via # gcp-docuploader # python-dateutil -tomli==2.0.1 \ - --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ - --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f +tomli==2.0.2 \ + --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ + --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed # via nox twine==5.1.1 \ --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ @@ -510,28 +480,30 @@ twine==5.1.1 \ typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 - # via -r requirements.in -urllib3==2.2.2 \ - --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ - --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 + # via + # -r requirements.in + # rich +urllib3==2.2.3 \ + --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ + --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 # via # requests # twine -virtualenv==20.26.3 \ - --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ - --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 +virtualenv==20.26.6 \ + --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ + --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 # via nox -wheel==0.43.0 \ - --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ - --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 +wheel==0.44.0 \ + --hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \ + --hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49 # via -r requirements.in -zipp==3.19.2 \ - --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ - --hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c +zipp==3.20.2 \ + --hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \ + --hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==70.2.0 \ - --hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05 \ - --hash=sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1 +setuptools==75.1.0 \ + --hash=sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2 \ + --hash=sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538 # via -r requirements.in From 07dee60ba8fba568b320a03c024ae676c2bbcfde Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 29 Oct 2024 16:36:38 -0400 Subject: [PATCH 250/272] chore: update templated files (#500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update templated files * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- packages/proto-plus/.kokoro/docs/common.cfg | 6 +++--- packages/proto-plus/.kokoro/release/common.cfg | 6 ------ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index db2377a5652a..13fc69ce9fc9 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:56cd3192d58a72bcd10b031ff2917d75b6b3c2a39a2571c080f1d6c519d40330 -# created: 2024-10-22 + digest: sha256:5efdf8d38e5a22c1ec9e5541cbdfde56399bdffcb6f531183f84ac66052a8024 +# created: 2024-10-25 diff --git a/packages/proto-plus/.kokoro/docs/common.cfg b/packages/proto-plus/.kokoro/docs/common.cfg index 63d9d344287d..41c589d98039 100644 --- a/packages/proto-plus/.kokoro/docs/common.cfg +++ b/packages/proto-plus/.kokoro/docs/common.cfg @@ -30,9 +30,9 @@ env_vars: { env_vars: { key: "V2_STAGING_BUCKET" - # Push non-cloud library docs to `docs-staging-v2-staging` instead of the + # Push non-cloud library docs to `docs-staging-v2-dev` instead of the # Cloud RAD bucket `docs-staging-v2` - value: "docs-staging-v2-staging" + value: "docs-staging-v2-dev" } # It will upload the docker image after successful builds. @@ -64,4 +64,4 @@ before_action { keyname: "docuploader_service_account" } } -} \ No newline at end of file +} diff --git a/packages/proto-plus/.kokoro/release/common.cfg b/packages/proto-plus/.kokoro/release/common.cfg index e244d5ece4aa..02221c9d1419 100644 --- a/packages/proto-plus/.kokoro/release/common.cfg +++ b/packages/proto-plus/.kokoro/release/common.cfg @@ -33,12 +33,6 @@ before_action { } } -# Tokens needed to report release status back to GitHub -env_vars: { - key: "SECRET_MANAGER_KEYS" - value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" -} - # Store the packages we uploaded to PyPI. That way, we have a record of exactly # what we published, which we can use to generate SBOMs and attestations. action { From b2bce3bbc225bab31b857875a8487915a5b00ebe Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:12:18 +0000 Subject: [PATCH 251/272] chore(python): update dependencies in .kokoro/docker/docs (#504) * chore(python): update dependencies in .kokoro/docker/docs Source-Link: https://github.com/googleapis/synthtool/commit/59171c8f83f3522ce186e4d110d27e772da4ba7a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2ed982f884312e4883e01b5ab8af8b6935f0216a5a2d82928d273081fc3be562 * See https://github.com/googleapis/synthtool/pull/2033/files --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 ++-- .../proto-plus/.github/release-trigger.yml | 2 +- .../.kokoro/docker/docs/requirements.txt | 20 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 13fc69ce9fc9..6301519a9a05 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:5efdf8d38e5a22c1ec9e5541cbdfde56399bdffcb6f531183f84ac66052a8024 -# created: 2024-10-25 + digest: sha256:2ed982f884312e4883e01b5ab8af8b6935f0216a5a2d82928d273081fc3be562 +# created: 2024-11-12T12:09:45.821174897Z diff --git a/packages/proto-plus/.github/release-trigger.yml b/packages/proto-plus/.github/release-trigger.yml index 1e9cfcd3a5de..1bc7271817db 100644 --- a/packages/proto-plus/.github/release-trigger.yml +++ b/packages/proto-plus/.github/release-trigger.yml @@ -1,2 +1,2 @@ enabled: true - +multiScmName: proto-plus-python diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.txt b/packages/proto-plus/.kokoro/docker/docs/requirements.txt index 66eacc82f041..8bb0764594b1 100644 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.txt +++ b/packages/proto-plus/.kokoro/docker/docs/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --allow-unsafe --generate-hashes requirements.in @@ -8,9 +8,9 @@ argcomplete==3.5.1 \ --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 # via nox -colorlog==6.8.2 \ - --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ - --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 +colorlog==6.9.0 \ + --hash=sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff \ + --hash=sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2 # via nox distlib==0.3.9 \ --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ @@ -24,9 +24,9 @@ nox==2024.10.9 \ --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 # via -r requirements.in -packaging==24.1 \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 +packaging==24.2 \ + --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ + --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f # via nox platformdirs==4.3.6 \ --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ @@ -36,7 +36,7 @@ tomli==2.0.2 \ --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed # via nox -virtualenv==20.26.6 \ - --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ - --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 +virtualenv==20.27.1 \ + --hash=sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba \ + --hash=sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4 # via nox From fdfc91fd836d2123cee3f9baaea980492295ac6a Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:35:53 +0000 Subject: [PATCH 252/272] chore(python): Update the python version in docs presubmit to use 3.10 (#511) * chore(python): Update the python version in docs presubmit to use 3.10 Source-Link: https://github.com/googleapis/synthtool/commit/de3def663b75d8b9ae1e5d548364c960ff13af8f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:a1c5112b81d645f5bbc4d4bbc99d7dcb5089a52216c0e3fb1203a0eeabadd7d5 * fix docs build --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/.OwlBot.lock.yaml | 6 +-- .../proto-plus/.github/workflows/tests.yml | 4 +- .../.kokoro/docker/docs/requirements.txt | 52 +++++++++++++++---- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 6301519a9a05..1d0fd7e7878b 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2025 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:2ed982f884312e4883e01b5ab8af8b6935f0216a5a2d82928d273081fc3be562 -# created: 2024-11-12T12:09:45.821174897Z + digest: sha256:a1c5112b81d645f5bbc4d4bbc99d7dcb5089a52216c0e3fb1203a0eeabadd7d5 +# created: 2025-01-02T23:09:36.975468657Z diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index de787dfd365f..f87ff1e90a82 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -30,10 +30,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.10" - name: Install nox. run: python -m pip install nox - name: Build the documentation. diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.txt b/packages/proto-plus/.kokoro/docker/docs/requirements.txt index 8bb0764594b1..f99a5c4aac7f 100644 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.txt +++ b/packages/proto-plus/.kokoro/docker/docs/requirements.txt @@ -2,11 +2,11 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes requirements.in +# pip-compile --allow-unsafe --generate-hashes synthtool/gcp/templates/python_library/.kokoro/docker/docs/requirements.in # -argcomplete==3.5.1 \ - --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ - --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 +argcomplete==3.5.2 \ + --hash=sha256:036d020d79048a5d525bc63880d7a4b8d1668566b8a76daf1144c0bbe0f63472 \ + --hash=sha256:23146ed7ac4403b70bd6026402468942ceba34a6732255b9edf5b7354f68a6bb # via nox colorlog==6.9.0 \ --hash=sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff \ @@ -23,7 +23,7 @@ filelock==3.16.1 \ nox==2024.10.9 \ --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 - # via -r requirements.in + # via -r synthtool/gcp/templates/python_library/.kokoro/docker/docs/requirements.in packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f @@ -32,11 +32,41 @@ platformdirs==4.3.6 \ --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via virtualenv -tomli==2.0.2 \ - --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ - --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 # via nox -virtualenv==20.27.1 \ - --hash=sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba \ - --hash=sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4 +virtualenv==20.28.0 \ + --hash=sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0 \ + --hash=sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa # via nox From 2806341918b3729479b3f55e9d888eece01c00ac Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 21 Jan 2025 15:50:26 -0500 Subject: [PATCH 253/272] fix: construct messages with nested duration in protobuf 5.28+ (#519) * fix: construct messages with nested duration in protobuf 5.28+ * Valueerror -> ValueError Co-authored-by: Victor Chudnovsky * Use public issue links Co-authored-by: Victor Chudnovsky * rename variables in test --------- Co-authored-by: Victor Chudnovsky --- .../proto-plus/proto/marshal/rules/message.py | 7 ++++--- .../tests/test_marshal_types_dates.py | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/proto-plus/proto/marshal/rules/message.py b/packages/proto-plus/proto/marshal/rules/message.py index 479a2d952776..c191180102fa 100644 --- a/packages/proto-plus/proto/marshal/rules/message.py +++ b/packages/proto-plus/proto/marshal/rules/message.py @@ -34,14 +34,15 @@ def to_proto(self, value): try: # Try the fast path first. return self._descriptor(**value) - except (TypeError, ValueError) as ex: - # If we have a TypeError or Valueerror, + except (TypeError, ValueError, AttributeError) as ex: + # If we have a TypeError, ValueError or AttributeError, # try the slow path in case the error # was: # - an int64/string issue. # - a missing key issue in case a key only exists with a `_` suffix. # See related issue: https://github.com/googleapis/python-api-core/issues/227. - # - a missing key issue due to nested struct. See: b/321905145. + # - a missing key issue due to nested struct. See: https://github.com/googleapis/proto-plus-python/issues/424. + # - a missing key issue due to nested duration. See: https://github.com/googleapis/google-cloud-python/issues/13350. return self._wrapper(value)._pb return value diff --git a/packages/proto-plus/tests/test_marshal_types_dates.py b/packages/proto-plus/tests/test_marshal_types_dates.py index 21841b990a22..44c65ebc778d 100644 --- a/packages/proto-plus/tests/test_marshal_types_dates.py +++ b/packages/proto-plus/tests/test_marshal_types_dates.py @@ -241,6 +241,26 @@ class Foo(proto.Message): assert Foo.pb(foo).ttl.seconds == 120 +def test_duration_write_string_nested(): + class Foo(proto.Message): + foo_field: duration_pb2.Duration = proto.Field( + proto.MESSAGE, + number=1, + message=duration_pb2.Duration, + ) + + Foo(foo_field="300s") + + class Bar(proto.Message): + bar_field = proto.Field(proto.MESSAGE, number=1, message=Foo) + + bar = Bar({"bar_field": {"foo_field": "300s"}}) + assert isinstance(bar.bar_field.foo_field, timedelta) + assert isinstance(Bar.pb(bar).bar_field.foo_field, duration_pb2.Duration) + assert bar.bar_field.foo_field.seconds == 300 + assert Bar.pb(bar).bar_field.foo_field.seconds == 300 + + def test_duration_del(): class Foo(proto.Message): ttl = proto.Field( From f50516e51280b1ad1ef2f135d7b1a7e8713794b1 Mon Sep 17 00:00:00 2001 From: Bryan Ricker <978899+bricker@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:52:44 -0800 Subject: [PATCH 254/272] fix: incorrect return type annotation for Message.to_dict (#516) The `proto.Message.to_dict` function returns a `dict[str, Any]`, but its return type annotation is incorrectly set to `Message`. The `proto.Message.to_dict` function returns the result of calling [`google.protobuf.json_format.MessageToDict()`](https://github.com/protocolbuffers/protobuf/blob/c58621b6ff2e6145ffb14c7e5a7af7648dfc0535/python/google/protobuf/json_format.py#L125-L162), which returns `dict[str, Any]` as specified in the [protobuf typeshed stubs](https://github.com/python/typeshed/blob/main/stubs/protobuf/google/protobuf/json_format.pyi#L23-L30). Additionally, runtime introspection shows that the function returns a `dict`. Co-authored-by: Anthonios Partheniou --- packages/proto-plus/proto/message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-plus/proto/message.py b/packages/proto-plus/proto/message.py index 989c1cd32a86..10a6f42d7612 100644 --- a/packages/proto-plus/proto/message.py +++ b/packages/proto-plus/proto/message.py @@ -16,7 +16,7 @@ import collections.abc import copy import re -from typing import List, Optional, Type +from typing import Any, Dict, List, Optional, Type import warnings import google.protobuf @@ -558,7 +558,7 @@ def to_dict( including_default_value_fields=None, float_precision=None, always_print_fields_with_no_presence=None, - ) -> "Message": + ) -> Dict[str, Any]: """Given a message instance, return its representation as a python dict. Args: From dab16e036222eaf1643553089857f31da42b6cc6 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:54:45 +0000 Subject: [PATCH 255/272] chore(python): fix docs publish build (#514) Source-Link: https://github.com/googleapis/synthtool/commit/bd9ede2fea1b640b7e90d5a1d110e6b300a2b43f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:04c35dc5f49f0f503a306397d6d043685f8d2bb822ab515818c4208d7fb2db3a Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/proto-plus/.github/.OwlBot.lock.yaml | 4 +- .../.kokoro/docker/docs/requirements.in | 1 + .../.kokoro/docker/docs/requirements.txt | 243 +++++++++++++++++- packages/proto-plus/.kokoro/publish-docs.sh | 4 - 4 files changed, 237 insertions(+), 15 deletions(-) diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml index 1d0fd7e7878b..4c0027ff1c61 100644 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ b/packages/proto-plus/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:a1c5112b81d645f5bbc4d4bbc99d7dcb5089a52216c0e3fb1203a0eeabadd7d5 -# created: 2025-01-02T23:09:36.975468657Z + digest: sha256:04c35dc5f49f0f503a306397d6d043685f8d2bb822ab515818c4208d7fb2db3a +# created: 2025-01-16T15:24:11.364245182Z diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.in b/packages/proto-plus/.kokoro/docker/docs/requirements.in index 816817c672a1..586bd07037ae 100644 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.in +++ b/packages/proto-plus/.kokoro/docker/docs/requirements.in @@ -1 +1,2 @@ nox +gcp-docuploader diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.txt b/packages/proto-plus/.kokoro/docker/docs/requirements.txt index f99a5c4aac7f..a9360a25b707 100644 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.txt +++ b/packages/proto-plus/.kokoro/docker/docs/requirements.txt @@ -2,16 +2,124 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes synthtool/gcp/templates/python_library/.kokoro/docker/docs/requirements.in +# pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.5.2 \ - --hash=sha256:036d020d79048a5d525bc63880d7a4b8d1668566b8a76daf1144c0bbe0f63472 \ - --hash=sha256:23146ed7ac4403b70bd6026402468942ceba34a6732255b9edf5b7354f68a6bb +argcomplete==3.5.3 \ + --hash=sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61 \ + --hash=sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392 # via nox +cachetools==5.5.0 \ + --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ + --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a + # via google-auth +certifi==2024.12.14 \ + --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \ + --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db + # via requests +charset-normalizer==3.4.1 \ + --hash=sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537 \ + --hash=sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa \ + --hash=sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a \ + --hash=sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294 \ + --hash=sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b \ + --hash=sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd \ + --hash=sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601 \ + --hash=sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd \ + --hash=sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4 \ + --hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \ + --hash=sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2 \ + --hash=sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313 \ + --hash=sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd \ + --hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \ + --hash=sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8 \ + --hash=sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1 \ + --hash=sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2 \ + --hash=sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496 \ + --hash=sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d \ + --hash=sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b \ + --hash=sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e \ + --hash=sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a \ + --hash=sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4 \ + --hash=sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca \ + --hash=sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78 \ + --hash=sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408 \ + --hash=sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5 \ + --hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \ + --hash=sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f \ + --hash=sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a \ + --hash=sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765 \ + --hash=sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6 \ + --hash=sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146 \ + --hash=sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6 \ + --hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \ + --hash=sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd \ + --hash=sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c \ + --hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \ + --hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \ + --hash=sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176 \ + --hash=sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770 \ + --hash=sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824 \ + --hash=sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f \ + --hash=sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf \ + --hash=sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487 \ + --hash=sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d \ + --hash=sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd \ + --hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \ + --hash=sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534 \ + --hash=sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f \ + --hash=sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b \ + --hash=sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9 \ + --hash=sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd \ + --hash=sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125 \ + --hash=sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9 \ + --hash=sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de \ + --hash=sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11 \ + --hash=sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d \ + --hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \ + --hash=sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f \ + --hash=sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda \ + --hash=sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7 \ + --hash=sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a \ + --hash=sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971 \ + --hash=sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8 \ + --hash=sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41 \ + --hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \ + --hash=sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f \ + --hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \ + --hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \ + --hash=sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886 \ + --hash=sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77 \ + --hash=sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76 \ + --hash=sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247 \ + --hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \ + --hash=sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb \ + --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \ + --hash=sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e \ + --hash=sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6 \ + --hash=sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037 \ + --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \ + --hash=sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e \ + --hash=sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807 \ + --hash=sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407 \ + --hash=sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c \ + --hash=sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12 \ + --hash=sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3 \ + --hash=sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089 \ + --hash=sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd \ + --hash=sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e \ + --hash=sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 \ + --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616 + # via requests +click==8.1.8 \ + --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ + --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a + # via gcp-docuploader colorlog==6.9.0 \ --hash=sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff \ --hash=sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2 - # via nox + # via + # gcp-docuploader + # nox distlib==0.3.9 \ --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 @@ -20,10 +128,78 @@ filelock==3.16.1 \ --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 # via virtualenv +gcp-docuploader==0.6.5 \ + --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ + --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea + # via -r requirements.in +google-api-core==2.24.0 \ + --hash=sha256:10d82ac0fca69c82a25b3efdeefccf6f28e02ebb97925a8cce8edbfe379929d9 \ + --hash=sha256:e255640547a597a4da010876d333208ddac417d60add22b6851a0c66a831fcaf + # via + # google-cloud-core + # google-cloud-storage +google-auth==2.37.0 \ + --hash=sha256:0054623abf1f9c83492c63d3f47e77f0a544caa3d40b2d98e099a611c2dd5d00 \ + --hash=sha256:42664f18290a6be591be5329a96fe30184be1a1badb7292a7f686a9659de9ca0 + # via + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.4.1 \ + --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ + --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 + # via google-cloud-storage +google-cloud-storage==2.19.0 \ + --hash=sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba \ + --hash=sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2 + # via gcp-docuploader +google-crc32c==1.6.0 \ + --hash=sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24 \ + --hash=sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d \ + --hash=sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e \ + --hash=sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57 \ + --hash=sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2 \ + --hash=sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8 \ + --hash=sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc \ + --hash=sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42 \ + --hash=sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f \ + --hash=sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa \ + --hash=sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b \ + --hash=sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc \ + --hash=sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760 \ + --hash=sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d \ + --hash=sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7 \ + --hash=sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d \ + --hash=sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0 \ + --hash=sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3 \ + --hash=sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3 \ + --hash=sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00 \ + --hash=sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871 \ + --hash=sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c \ + --hash=sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9 \ + --hash=sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205 \ + --hash=sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc \ + --hash=sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d \ + --hash=sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4 + # via + # google-cloud-storage + # google-resumable-media +google-resumable-media==2.7.2 \ + --hash=sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa \ + --hash=sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0 + # via google-cloud-storage +googleapis-common-protos==1.66.0 \ + --hash=sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c \ + --hash=sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed + # via google-api-core +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via requests nox==2024.10.9 \ --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 - # via -r synthtool/gcp/templates/python_library/.kokoro/docker/docs/requirements.in + # via -r requirements.in packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f @@ -32,6 +208,51 @@ platformdirs==4.3.6 \ --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via virtualenv +proto-plus==1.25.0 \ + --hash=sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961 \ + --hash=sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91 + # via google-api-core +protobuf==5.29.3 \ + --hash=sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f \ + --hash=sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7 \ + --hash=sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888 \ + --hash=sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620 \ + --hash=sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da \ + --hash=sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252 \ + --hash=sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a \ + --hash=sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e \ + --hash=sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107 \ + --hash=sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f \ + --hash=sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84 + # via + # gcp-docuploader + # google-api-core + # googleapis-common-protos + # proto-plus +pyasn1==0.6.1 \ + --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ + --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.4.1 \ + --hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \ + --hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c + # via google-auth +requests==2.32.3 \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 + # via + # google-api-core + # google-cloud-storage +rsa==4.9 \ + --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ + --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 + # via google-auth +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via gcp-docuploader tomli==2.2.1 \ --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ @@ -66,7 +287,11 @@ tomli==2.2.1 \ --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 # via nox -virtualenv==20.28.0 \ - --hash=sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0 \ - --hash=sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa +urllib3==2.3.0 \ + --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ + --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d + # via requests +virtualenv==20.28.1 \ + --hash=sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb \ + --hash=sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329 # via nox diff --git a/packages/proto-plus/.kokoro/publish-docs.sh b/packages/proto-plus/.kokoro/publish-docs.sh index 233205d580e9..4ed4aaf1346f 100755 --- a/packages/proto-plus/.kokoro/publish-docs.sh +++ b/packages/proto-plus/.kokoro/publish-docs.sh @@ -20,10 +20,6 @@ export PYTHONUNBUFFERED=1 export PATH="${HOME}/.local/bin:${PATH}" -# Install nox -python3.10 -m pip install --require-hashes -r .kokoro/requirements.txt -python3.10 -m nox --version - # build docs nox -s docs From 9f548fb4fe1e84a1506373c3ebe9560318dcebaf Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Tue, 21 Jan 2025 21:27:47 +0000 Subject: [PATCH 256/272] fix: Port to Sphinx 8.0 (#509) The old `intersphinx_mapping` format has been removed; it must now map identifiers to (target, inventory) tuples. Co-authored-by: Anthonios Partheniou --- packages/proto-plus/docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/docs/conf.py b/packages/proto-plus/docs/conf.py index aced5d30752f..ecc295d25a44 100644 --- a/packages/proto-plus/docs/conf.py +++ b/packages/proto-plus/docs/conf.py @@ -179,4 +179,4 @@ # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"https://docs.python.org/3/": None} +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} From 43f3f3b3a9ed04b719de7a7eb35b61a8b7bf201f Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 22 Jan 2025 10:26:49 -0500 Subject: [PATCH 257/272] fix: Use include rather than exclude to find_namespace_packages in setup.py (#502) https://setuptools.pypa.io/en/latest/userguide/package_discovery.html has a warning: > Please have in mind that find_namespace: (setup.cfg), > find_namespace_packages() (setup.py) and find (pyproject.toml) > will scan all folders that you have in your project directory > if you use a flat-layout. That applies here. Without this change, if you run: 'python3 setup.py bdist_wheel' then you may end up with build/ in your wheel, and in some cases even docs/ and testing/ . The fix is to only include proto and proto.*. Signed-off-by: Scott Moser Co-authored-by: Anthonios Partheniou --- packages/proto-plus/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index d8a430f21b53..6f8d8970156b 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -37,7 +37,7 @@ author="Google LLC", author_email="googleapis-packages@google.com", url="https://github.com/googleapis/proto-plus-python.git", - packages=find_namespace_packages(exclude=["docs", "tests"]), + packages=find_namespace_packages(include=["proto", "proto.*"]), description="Beautiful, Pythonic protocol buffers.", long_description=README, platforms="Posix; MacOS X", From 702eb8283e60cec993fab238ffc0e92a332b8a53 Mon Sep 17 00:00:00 2001 From: Rin Arakaki Date: Thu, 23 Jan 2025 00:59:11 +0900 Subject: [PATCH 258/272] feat: migrate to pyproject.toml (#496) * migrate to pyproject.toml * Rebase following https://github.com/googleapis/proto-plus-python/pull/502 * add entries such as documentation and classifiers which exist in other projects * Use correct url for docs --------- Co-authored-by: Anthonios Partheniou --- packages/proto-plus/pyproject.toml | 58 ++++++++++++++++++++++++++++++ packages/proto-plus/setup.py | 55 ++-------------------------- 2 files changed, 60 insertions(+), 53 deletions(-) create mode 100644 packages/proto-plus/pyproject.toml diff --git a/packages/proto-plus/pyproject.toml b/packages/proto-plus/pyproject.toml new file mode 100644 index 000000000000..6fb7cbe21e42 --- /dev/null +++ b/packages/proto-plus/pyproject.toml @@ -0,0 +1,58 @@ +# 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "proto-plus" +authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }] +license = { text = "Apache 2.0" } +requires-python = ">=3.7" +description = "Beautiful, Pythonic protocol buffers" +readme = "README.rst" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Operating System :: OS Independent", + "Topic :: Software Development :: Code Generators", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = ["protobuf >=3.19.0, < 6.0.0dev"] +dynamic = ["version"] + +[project.urls] +Documentation = "https://googleapis.dev/python/proto-plus/latest/" +Repository = "https://github.com/googleapis/proto-plus-python" + +[project.optional-dependencies] +testing = ["google-api-core >= 1.31.5"] + +[tool.setuptools.dynamic] +version = { attr = "proto.version.__version__" } + +[tool.setuptools.packages.find] +# Only include packages under the 'proto' namespace. Do not include build, docs, tests +include = ["proto*"] diff --git a/packages/proto-plus/setup.py b/packages/proto-plus/setup.py index 6f8d8970156b..12e9ed26dcdf 100644 --- a/packages/proto-plus/setup.py +++ b/packages/proto-plus/setup.py @@ -12,59 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io -import re -import os -from setuptools import find_namespace_packages, setup +from setuptools import setup -PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) -version = None - -with open(os.path.join(PACKAGE_ROOT, "proto/version.py")) as fp: - version_candidates = re.findall(r"(?<=\")\d+.\d+.\d+(?=\")", fp.read()) - assert len(version_candidates) == 1 - version = version_candidates[0] - -with io.open(os.path.join(PACKAGE_ROOT, "README.rst")) as file_obj: - README = file_obj.read() - -setup( - name="proto-plus", - version=version, - license="Apache 2.0", - author="Google LLC", - author_email="googleapis-packages@google.com", - url="https://github.com/googleapis/proto-plus-python.git", - packages=find_namespace_packages(include=["proto", "proto.*"]), - description="Beautiful, Pythonic protocol buffers.", - long_description=README, - platforms="Posix; MacOS X", - include_package_data=True, - install_requires=("protobuf >=3.19.0, <6.0.0dev",), - extras_require={ - "testing": [ - "google-api-core >= 1.31.5", - ], - }, - python_requires=">=3.7", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Operating System :: POSIX", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Topic :: Software Development :: Code Generators", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - zip_safe=False, -) +setup() From d51ae368b16a53a3dd9f3d8d6a901461cce93136 Mon Sep 17 00:00:00 2001 From: airbender-1 <145376293+airbender-1@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:00:36 -0500 Subject: [PATCH 259/272] fix: fix enums initialization in PyPy (#507) * fix: enums initialization in PyPy When removing the _pb_options name from the enum attributes, enums initialization in PyPy should not raise exception. Refs: #506 * test: add presubmit for PyPy * Add unit (pypy3.10, python) as a required check --------- Co-authored-by: Anthonios Partheniou --- .../.github/sync-repo-settings.yaml | 1 + .../proto-plus/.github/workflows/tests.yml | 27 +++++++++++-------- packages/proto-plus/noxfile.py | 1 + packages/proto-plus/proto/enums.py | 2 ++ .../testing/constraints-pypy3.10.txt | 0 5 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 packages/proto-plus/testing/constraints-pypy3.10.txt diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index d66f708a6c1c..4a9e75a9fd96 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -19,6 +19,7 @@ branchProtectionRules: - 'unit (3.10, cpp)' - 'unit (3.10, python)' - 'unit (3.10, upb)' + - 'unit (pypy3.10, python)' - 'unit (3.11, python)' - 'unit (3.11, upb)' - 'unit (3.12, python)' diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index f87ff1e90a82..472f703fd14f 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -42,7 +42,10 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + # See https://github.com/actions/setup-python?tab=readme-ov-file#basic-usage + # for the format of the entries in 'python'. + # See https://downloads.python.org/pypy/ for the current supported versions of PyPy. + python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy3.10'] 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. @@ -56,6 +59,14 @@ jobs: python: 3.12 - variant: "cpp" python: 3.13 + # In PyPy 3.10, we see the following warning + # UserWarning: Selected implementation upb is not available. Falling back to the python implementation. + - variant: "upb" + python: 'pypy3.10' + # In PyPy 3.10, we see the following warning + # UserWarning: PyPy does not work yet with cpp protocol buffers. Falling back to the python implementation. + - variant: "cpp" + python: 'pypy3.10' steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} @@ -66,22 +77,16 @@ jobs: - name: Install nox run: | pip install nox - # Trim the Python version string - - name: Trim python version - run: | - PYTHON_VERSION_TRIMMED=${{ matrix.python }} - PYTHON_VERSION_TRIMMED=$(echo $PYTHON_VERSION_TRIMMED | cut -c1-4) - echo "PYTHON_VERSION_TRIMMED=$PYTHON_VERSION_TRIMMED" >> $GITHUB_ENV - name: Run unit tests env: - COVERAGE_FILE: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} + COVERAGE_FILE: .coverage-${{ matrix.variant }}-${{ matrix.python }} run: | - nox -s "unit-${{ env.PYTHON_VERSION_TRIMMED }}(implementation='${{ matrix.variant }}')" + nox -s "unit-${{ matrix.python }}(implementation='${{ matrix.variant }}')" - name: Upload coverage results uses: actions/upload-artifact@v4 with: - name: coverage-artifact-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} - path: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} + name: coverage-artifact-${{ matrix.variant }}-${{ matrix.python }} + path: .coverage-${{ matrix.variant }}-${{ matrix.python }} include-hidden-files: true prerelease: runs-on: ubuntu-22.04 diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 1c16bc51843e..50307e99d54d 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -26,6 +26,7 @@ "3.8", "3.9", "3.10", + "pypy3.10", "3.11", "3.12", "3.13", diff --git a/packages/proto-plus/proto/enums.py b/packages/proto-plus/proto/enums.py index 4073c2a3c677..d3ab6b792023 100644 --- a/packages/proto-plus/proto/enums.py +++ b/packages/proto-plus/proto/enums.py @@ -61,6 +61,8 @@ def __new__(mcls, name, bases, attrs): if isinstance(attrs._member_names, list): idx = attrs._member_names.index(pb_options) attrs._member_names.pop(idx) + elif isinstance(attrs._member_names, set): # PyPy + attrs._member_names.discard(pb_options) else: # Python 3.11.0b3 del attrs._member_names[pb_options] diff --git a/packages/proto-plus/testing/constraints-pypy3.10.txt b/packages/proto-plus/testing/constraints-pypy3.10.txt new file mode 100644 index 000000000000..e69de29bb2d1 From 9ac449efafa610563bf6974852b96c689d198789 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 22 Jan 2025 11:01:04 -0500 Subject: [PATCH 260/272] docs: Update docs link in README (#524) --- packages/proto-plus/README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 871fdebb63e5..296d803ef232 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -18,11 +18,9 @@ Documentation `Documentation`_ is available on Read the Docs. -.. _documentation: https://proto-plus-python.readthedocs.io/en/latest/ +.. _documentation: https://googleapis.dev/python/proto-plus/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/proto-plus.svg :target: https://pypi.org/project/proto-plus .. |release level| image:: https://img.shields.io/badge/release%20level-ga-gold.svg?style=flat :target: https://cloud.google.com/terms/launch-stages -.. |docs| image:: https://readthedocs.org/projects/proto-plus-python/badge/?version=latest - :target: https://proto-plus-python.readthedocs.io/en/latest/ From 577eafe8679409a2ecd63b4a3c1d83c1ce6105de Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 22 Jan 2025 11:13:38 -0500 Subject: [PATCH 261/272] build: remove obsolete files (#525) --- packages/proto-plus/.kokoro/release.sh | 29 - .../proto-plus/.kokoro/release/common.cfg | 43 -- .../proto-plus/.kokoro/release/release.cfg | 1 - packages/proto-plus/.kokoro/requirements.in | 11 - packages/proto-plus/.kokoro/requirements.txt | 509 ------------------ 5 files changed, 593 deletions(-) delete mode 100755 packages/proto-plus/.kokoro/release.sh delete mode 100644 packages/proto-plus/.kokoro/release/common.cfg delete mode 100644 packages/proto-plus/.kokoro/release/release.cfg delete mode 100644 packages/proto-plus/.kokoro/requirements.in delete mode 100644 packages/proto-plus/.kokoro/requirements.txt diff --git a/packages/proto-plus/.kokoro/release.sh b/packages/proto-plus/.kokoro/release.sh deleted file mode 100755 index 74a2e4060323..000000000000 --- a/packages/proto-plus/.kokoro/release.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -# Start the releasetool reporter -python3 -m pip install --require-hashes -r github/proto-plus-python/.kokoro/requirements.txt -python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-3") -cd github/proto-plus-python -python3 setup.py sdist bdist_wheel -twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/proto-plus/.kokoro/release/common.cfg b/packages/proto-plus/.kokoro/release/common.cfg deleted file mode 100644 index 02221c9d1419..000000000000 --- a/packages/proto-plus/.kokoro/release/common.cfg +++ /dev/null @@ -1,43 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "proto-plus-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/proto-plus-python/.kokoro/release.sh" -} - -# Fetch PyPI password -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "google-cloud-pypi-token-keystore-3" - } - } -} - -# Store the packages we uploaded to PyPI. That way, we have a record of exactly -# what we published, which we can use to generate SBOMs and attestations. -action { - define_artifacts { - regex: "github/proto-plus-python/**/*.tar.gz" - strip_prefix: "github/proto-plus-python" - } -} diff --git a/packages/proto-plus/.kokoro/release/release.cfg b/packages/proto-plus/.kokoro/release/release.cfg deleted file mode 100644 index 8f43917d92fe..000000000000 --- a/packages/proto-plus/.kokoro/release/release.cfg +++ /dev/null @@ -1 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/proto-plus/.kokoro/requirements.in b/packages/proto-plus/.kokoro/requirements.in deleted file mode 100644 index fff4d9ce0d0a..000000000000 --- a/packages/proto-plus/.kokoro/requirements.in +++ /dev/null @@ -1,11 +0,0 @@ -gcp-docuploader -gcp-releasetool>=2 # required for compatibility with cryptography>=42.x -importlib-metadata -typing-extensions -twine -wheel -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/packages/proto-plus/.kokoro/requirements.txt b/packages/proto-plus/.kokoro/requirements.txt deleted file mode 100644 index 006d8ef931bf..000000000000 --- a/packages/proto-plus/.kokoro/requirements.txt +++ /dev/null @@ -1,509 +0,0 @@ -# -# 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.5.1 \ - --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ - --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 - # via nox -attrs==24.2.0 \ - --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \ - --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2 - # via gcp-releasetool -backports-tarfile==1.2.0 \ - --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ - --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 - # via jaraco-context -cachetools==5.5.0 \ - --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ - --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a - # via google-auth -certifi==2024.8.30 \ - --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \ - --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9 - # via requests -cffi==1.17.1 \ - --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ - --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ - --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ - --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ - --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ - --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ - --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ - --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ - --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ - --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ - --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ - --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ - --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ - --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ - --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ - --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ - --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ - --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ - --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ - --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ - --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ - --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ - --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ - --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ - --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ - --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ - --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ - --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ - --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ - --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ - --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ - --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ - --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ - --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ - --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ - --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ - --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ - --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ - --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ - --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ - --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ - --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ - --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ - --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ - --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ - --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ - --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ - --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ - --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ - --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ - --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ - --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ - --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ - --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ - --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ - --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ - --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ - --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ - --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ - --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ - --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ - --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ - --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ - --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ - --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ - --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ - --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b - # via cryptography -charset-normalizer==2.1.1 \ - --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ - --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f - # via - # -r requirements.in - # requests -click==8.0.4 \ - --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \ - --hash=sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb - # via - # -r requirements.in - # gcp-docuploader - # gcp-releasetool -colorlog==6.8.2 \ - --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ - --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 - # via - # gcp-docuploader - # nox -cryptography==43.0.1 \ - --hash=sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494 \ - --hash=sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806 \ - --hash=sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d \ - --hash=sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062 \ - --hash=sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2 \ - --hash=sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4 \ - --hash=sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1 \ - --hash=sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85 \ - --hash=sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84 \ - --hash=sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042 \ - --hash=sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d \ - --hash=sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962 \ - --hash=sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2 \ - --hash=sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa \ - --hash=sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d \ - --hash=sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365 \ - --hash=sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96 \ - --hash=sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47 \ - --hash=sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d \ - --hash=sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d \ - --hash=sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c \ - --hash=sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb \ - --hash=sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277 \ - --hash=sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172 \ - --hash=sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034 \ - --hash=sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a \ - --hash=sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289 - # via - # -r requirements.in - # gcp-releasetool - # secretstorage -distlib==0.3.9 \ - --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ - --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 - # via virtualenv -docutils==0.21.2 \ - --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ - --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 - # via readme-renderer -filelock==3.16.1 \ - --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ - --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 - # via virtualenv -gcp-docuploader==0.6.5 \ - --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ - --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea - # via -r requirements.in -gcp-releasetool==2.1.1 \ - --hash=sha256:25639269f4eae510094f9dbed9894977e1966933211eb155a451deebc3fc0b30 \ - --hash=sha256:845f4ded3d9bfe8cc7fdaad789e83f4ea014affa77785259a7ddac4b243e099e - # via -r requirements.in -google-api-core==2.21.0 \ - --hash=sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81 \ - --hash=sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d - # via - # google-cloud-core - # google-cloud-storage -google-auth==2.35.0 \ - --hash=sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f \ - --hash=sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a - # via - # gcp-releasetool - # google-api-core - # google-cloud-core - # google-cloud-storage -google-cloud-core==2.4.1 \ - --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ - --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 - # via google-cloud-storage -google-cloud-storage==2.18.2 \ - --hash=sha256:97a4d45c368b7d401ed48c4fdfe86e1e1cb96401c9e199e419d289e2c0370166 \ - --hash=sha256:aaf7acd70cdad9f274d29332673fcab98708d0e1f4dceb5a5356aaef06af4d99 - # via gcp-docuploader -google-crc32c==1.6.0 \ - --hash=sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24 \ - --hash=sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d \ - --hash=sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e \ - --hash=sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57 \ - --hash=sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2 \ - --hash=sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8 \ - --hash=sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc \ - --hash=sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42 \ - --hash=sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f \ - --hash=sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa \ - --hash=sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b \ - --hash=sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc \ - --hash=sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760 \ - --hash=sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d \ - --hash=sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7 \ - --hash=sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d \ - --hash=sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0 \ - --hash=sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3 \ - --hash=sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3 \ - --hash=sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00 \ - --hash=sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871 \ - --hash=sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c \ - --hash=sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9 \ - --hash=sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205 \ - --hash=sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc \ - --hash=sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d \ - --hash=sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4 - # via - # google-cloud-storage - # google-resumable-media -google-resumable-media==2.7.2 \ - --hash=sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa \ - --hash=sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0 - # via google-cloud-storage -googleapis-common-protos==1.65.0 \ - --hash=sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63 \ - --hash=sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0 - # via google-api-core -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 - # via requests -importlib-metadata==8.5.0 \ - --hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \ - --hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7 - # via - # -r requirements.in - # keyring - # twine -jaraco-classes==3.4.0 \ - --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ - --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 - # via keyring -jaraco-context==6.0.1 \ - --hash=sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3 \ - --hash=sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4 - # via keyring -jaraco-functools==4.1.0 \ - --hash=sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d \ - --hash=sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649 - # via keyring -jeepney==0.8.0 \ - --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ - --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 - # via - # keyring - # secretstorage -jinja2==3.1.4 \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d - # via gcp-releasetool -keyring==25.4.1 \ - --hash=sha256:5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf \ - --hash=sha256:b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b - # via - # gcp-releasetool - # twine -markdown-it-py==3.0.0 \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb - # via rich -markupsafe==3.0.1 \ - --hash=sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396 \ - --hash=sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38 \ - --hash=sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a \ - --hash=sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8 \ - --hash=sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b \ - --hash=sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad \ - --hash=sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a \ - --hash=sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a \ - --hash=sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da \ - --hash=sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6 \ - --hash=sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8 \ - --hash=sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344 \ - --hash=sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a \ - --hash=sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8 \ - --hash=sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5 \ - --hash=sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7 \ - --hash=sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170 \ - --hash=sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132 \ - --hash=sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9 \ - --hash=sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd \ - --hash=sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9 \ - --hash=sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346 \ - --hash=sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc \ - --hash=sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589 \ - --hash=sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5 \ - --hash=sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915 \ - --hash=sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295 \ - --hash=sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453 \ - --hash=sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea \ - --hash=sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b \ - --hash=sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d \ - --hash=sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b \ - --hash=sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4 \ - --hash=sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b \ - --hash=sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7 \ - --hash=sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf \ - --hash=sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f \ - --hash=sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91 \ - --hash=sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd \ - --hash=sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50 \ - --hash=sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b \ - --hash=sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583 \ - --hash=sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a \ - --hash=sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984 \ - --hash=sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c \ - --hash=sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c \ - --hash=sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25 \ - --hash=sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa \ - --hash=sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4 \ - --hash=sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3 \ - --hash=sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97 \ - --hash=sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1 \ - --hash=sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd \ - --hash=sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772 \ - --hash=sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a \ - --hash=sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729 \ - --hash=sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca \ - --hash=sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6 \ - --hash=sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635 \ - --hash=sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b \ - --hash=sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f - # via jinja2 -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba - # via markdown-it-py -more-itertools==10.5.0 \ - --hash=sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef \ - --hash=sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6 - # via - # jaraco-classes - # jaraco-functools -nh3==0.2.18 \ - --hash=sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164 \ - --hash=sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86 \ - --hash=sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b \ - --hash=sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad \ - --hash=sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204 \ - --hash=sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a \ - --hash=sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200 \ - --hash=sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189 \ - --hash=sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f \ - --hash=sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811 \ - --hash=sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844 \ - --hash=sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4 \ - --hash=sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be \ - --hash=sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50 \ - --hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \ - --hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe - # via readme-renderer -nox==2024.10.9 \ - --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ - --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 - # via -r requirements.in -packaging==24.1 \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 - # via - # gcp-releasetool - # nox -pkginfo==1.10.0 \ - --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ - --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 - # via twine -platformdirs==4.3.6 \ - --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ - --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb - # via virtualenv -proto-plus==1.24.0 \ - --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ - --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 - # via google-api-core -protobuf==5.28.2 \ - --hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \ - --hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \ - --hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \ - --hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \ - --hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \ - --hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \ - --hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \ - --hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \ - --hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \ - --hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \ - --hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d - # via - # gcp-docuploader - # gcp-releasetool - # google-api-core - # googleapis-common-protos - # proto-plus -pyasn1==0.6.1 \ - --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ - --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.4.1 \ - --hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \ - --hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c - # via google-auth -pycparser==2.22 \ - --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ - --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc - # via cffi -pygments==2.18.0 \ - --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ - --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a - # via - # readme-renderer - # rich -pyjwt==2.9.0 \ - --hash=sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850 \ - --hash=sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c - # via gcp-releasetool -pyperclip==1.9.0 \ - --hash=sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310 - # via gcp-releasetool -python-dateutil==2.9.0.post0 \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 - # via gcp-releasetool -readme-renderer==44.0 \ - --hash=sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 \ - --hash=sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1 - # via twine -requests==2.32.3 \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 - # via - # gcp-releasetool - # google-api-core - # google-cloud-storage - # requests-toolbelt - # twine -requests-toolbelt==1.0.0 \ - --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ - --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 - # via twine -rfc3986==2.0.0 \ - --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ - --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c - # via twine -rich==13.9.2 \ - --hash=sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c \ - --hash=sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1 - # via twine -rsa==4.9 \ - --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ - --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 - # via google-auth -secretstorage==3.3.3 \ - --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ - --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 - # via keyring -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # gcp-docuploader - # python-dateutil -tomli==2.0.2 \ - --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ - --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed - # via nox -twine==5.1.1 \ - --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ - --hash=sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db - # via -r requirements.in -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 - # via - # -r requirements.in - # rich -urllib3==2.2.3 \ - --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ - --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 - # via - # requests - # twine -virtualenv==20.26.6 \ - --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ - --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 - # via nox -wheel==0.44.0 \ - --hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \ - --hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49 - # via -r requirements.in -zipp==3.20.2 \ - --hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \ - --hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.1.0 \ - --hash=sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2 \ - --hash=sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538 - # via -r requirements.in From 7cbf4cebff0a22de0011df2b5bb089f8fe9a4635 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 22 Jan 2025 12:39:00 -0500 Subject: [PATCH 262/272] chore: add presubmit for linting setup.py (#527) * chore: add presubmit for linting setup.py * fix duplicate implicit target name in README.rst * add action to lint setup.py * style * add new line --- .../.github/sync-repo-settings.yaml | 1 + .../proto-plus/.github/workflows/lint.yml | 22 +++++++++++++++++++ packages/proto-plus/README.rst | 4 ++-- packages/proto-plus/noxfile.py | 8 +++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 packages/proto-plus/.github/workflows/lint.yml diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index 4a9e75a9fd96..2f30ae789095 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -7,6 +7,7 @@ branchProtectionRules: requiredStatusCheckContexts: - 'style-check' - 'docs' + - 'lint' - 'unit (3.7, cpp)' - 'unit (3.7, python)' - 'unit (3.7, upb)' diff --git a/packages/proto-plus/.github/workflows/lint.yml b/packages/proto-plus/.github/workflows/lint.yml new file mode 100644 index 000000000000..08c2b5d05fca --- /dev/null +++ b/packages/proto-plus/.github/workflows/lint.yml @@ -0,0 +1,22 @@ +on: + pull_request: + branches: + - main +name: lint +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run lint_setup_py + run: | + nox -s lint_setup_py diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 296d803ef232..16e4fb596c41 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -16,9 +16,9 @@ largely behave like native Python types. Documentation ------------- -`Documentation`_ is available on Read the Docs. +`API Documentation`_ is available on Read the Docs. -.. _documentation: https://googleapis.dev/python/proto-plus/latest/ +.. _API Documentation: https://googleapis.dev/python/proto-plus/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/proto-plus.svg :target: https://pypi.org/project/proto-plus diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 50307e99d54d..01a65b095fa2 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -161,3 +161,11 @@ def docs(session): "docs/", "docs/_build/html/", ) + + +@nox.session(python="3.10") +def lint_setup_py(session): + """Verify that setup.py is valid (including RST check).""" + + session.install("docutils", "Pygments") + session.run("python", "setup.py", "check", "--restructuredtext", "--strict") From 49b1fcfd5ae76bfdec6344464fa3c29e08735c8e Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 22 Jan 2025 13:07:02 -0500 Subject: [PATCH 263/272] chore: remove broken link (#528) --- packages/proto-plus/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/README.rst b/packages/proto-plus/README.rst index 16e4fb596c41..0fcef70f8734 100644 --- a/packages/proto-plus/README.rst +++ b/packages/proto-plus/README.rst @@ -1,7 +1,7 @@ Proto Plus for Python ===================== -|pypi| |release level| |docs| +|pypi| |release level| Beautiful, Pythonic protocol buffers. From 9f1f50f78c52f6fae887c43a4395dc8bdcca4367 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:16:31 -0500 Subject: [PATCH 264/272] chore(main): release 1.26.0 (#522) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Anthonios Partheniou --- packages/proto-plus/CHANGELOG.md | 20 ++++++++++++++++++++ packages/proto-plus/proto/version.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index 238107e8df5e..ca7367894eac 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.26.0](https://github.com/googleapis/proto-plus-python/compare/v1.25.0...v1.26.0) (2025-01-22) + + +### Features + +* Migrate to pyproject.toml ([#496](https://github.com/googleapis/proto-plus-python/issues/496)) ([82ed3b9](https://github.com/googleapis/proto-plus-python/commit/82ed3b91ae0cebd6f89ce6661590a1bc6b7fce31)) + + +### Bug Fixes + +* Construct messages with nested duration in protobuf 5.28+ ([#519](https://github.com/googleapis/proto-plus-python/issues/519)) ([197ddf8](https://github.com/googleapis/proto-plus-python/commit/197ddf8a3ae9ab21b0136f27692d0f1ecd727d5b)) +* Fix enums initialization in PyPy ([#507](https://github.com/googleapis/proto-plus-python/issues/507)) ([b8b68f2](https://github.com/googleapis/proto-plus-python/commit/b8b68f207a00129e91551ef6725f5021f821e0a9)) +* Incorrect return type annotation for Message.to_dict ([#516](https://github.com/googleapis/proto-plus-python/issues/516)) ([72990f3](https://github.com/googleapis/proto-plus-python/commit/72990f3859d77732d40db5b82c310da265e72cac)) +* Use include rather than exclude to find_namespace_packages in setup.py ([#502](https://github.com/googleapis/proto-plus-python/issues/502)) ([77e252e](https://github.com/googleapis/proto-plus-python/commit/77e252e614f6434c2c47ab6168d08f87e004be43)) + + +### Documentation + +* Update docs link in README ([#524](https://github.com/googleapis/proto-plus-python/issues/524)) ([a85be75](https://github.com/googleapis/proto-plus-python/commit/a85be75f8fb811f5345cea2786b9b7a688085a7e)) + ## [1.25.0](https://github.com/googleapis/proto-plus-python/compare/v1.24.0...v1.25.0) (2024-10-15) diff --git a/packages/proto-plus/proto/version.py b/packages/proto-plus/proto/version.py index ab2f7680f172..0c2c9d924e3c 100644 --- a/packages/proto-plus/proto/version.py +++ b/packages/proto-plus/proto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.25.0" +__version__ = "1.26.0" From abe9d52306c4a136a7676544b797179ce457a631 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 28 Jan 2025 12:54:57 -0500 Subject: [PATCH 265/272] build: Add presubmits for Python 3.14 pre-release (#532) --- packages/proto-plus/.github/sync-repo-settings.yaml | 6 ++++-- packages/proto-plus/.github/workflows/tests.yml | 6 ++++-- packages/proto-plus/noxfile.py | 1 + packages/proto-plus/testing/constraints-3.14.txt | 0 4 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 packages/proto-plus/testing/constraints-3.14.txt diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml index 2f30ae789095..e0e29f0876e9 100644 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ b/packages/proto-plus/.github/sync-repo-settings.yaml @@ -27,8 +27,10 @@ branchProtectionRules: - 'unit (3.12, upb)' - 'unit (3.13, python)' - 'unit (3.13, upb)' - - 'prerelease (3.13, python)' - - 'prerelease (3.13, upb)' + - 'unit (3.14, python)' + - 'unit (3.14, upb)' + - 'prerelease (3.14, python)' + - 'prerelease (3.14, upb)' - cover - OwlBot Post Processor - 'cla/google' diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml index 472f703fd14f..77bfc9ad7d4b 100644 --- a/packages/proto-plus/.github/workflows/tests.yml +++ b/packages/proto-plus/.github/workflows/tests.yml @@ -45,7 +45,7 @@ jobs: # See https://github.com/actions/setup-python?tab=readme-ov-file#basic-usage # for the format of the entries in 'python'. # See https://downloads.python.org/pypy/ for the current supported versions of PyPy. - python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy3.10'] + python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy3.10'] 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. @@ -59,6 +59,8 @@ jobs: python: 3.12 - variant: "cpp" python: 3.13 + - variant: "cpp" + python: 3.14 # In PyPy 3.10, we see the following warning # UserWarning: Selected implementation upb is not available. Falling back to the python implementation. - variant: "upb" @@ -92,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python: ['3.13'] + python: ['3.14'] variant: ['python', 'upb'] steps: - uses: actions/checkout@v4 diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 01a65b095fa2..1e43d8264ac8 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -30,6 +30,7 @@ "3.11", "3.12", "3.13", + "3.14", ] # Error if a python version is missing diff --git a/packages/proto-plus/testing/constraints-3.14.txt b/packages/proto-plus/testing/constraints-3.14.txt new file mode 100644 index 000000000000..e69de29bb2d1 From cfec2bbdb042addb3d5ce900bbcfc46b532ee7d6 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 5 Mar 2025 16:41:29 -0500 Subject: [PATCH 266/272] fix(deps): allow protobuf 6.x (#536) * fix(deps): allow protobuf 6.x * Remove 'dev' --- packages/proto-plus/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-plus/pyproject.toml b/packages/proto-plus/pyproject.toml index 6fb7cbe21e42..3a031d6640da 100644 --- a/packages/proto-plus/pyproject.toml +++ b/packages/proto-plus/pyproject.toml @@ -40,7 +40,7 @@ classifiers = [ "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", ] -dependencies = ["protobuf >=3.19.0, < 6.0.0dev"] +dependencies = ["protobuf >=3.19.0, < 7.0.0"] dynamic = ["version"] [project.urls] From 3a8e99a98a607fd45b57bed8cb1bd00819baa348 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 20:47:54 +0500 Subject: [PATCH 267/272] chore(main): release 1.26.1 (#538) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/proto-plus/CHANGELOG.md | 7 +++++++ packages/proto-plus/proto/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index ca7367894eac..c0dea7c12f4b 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.26.1](https://github.com/googleapis/proto-plus-python/compare/v1.26.0...v1.26.1) (2025-03-05) + + +### Bug Fixes + +* **deps:** Allow protobuf 6.x ([#536](https://github.com/googleapis/proto-plus-python/issues/536)) ([51ba025](https://github.com/googleapis/proto-plus-python/commit/51ba02513c4fa12fe94db74c4a23fed7af972ea9)) + ## [1.26.0](https://github.com/googleapis/proto-plus-python/compare/v1.25.0...v1.26.0) (2025-01-22) diff --git a/packages/proto-plus/proto/version.py b/packages/proto-plus/proto/version.py index 0c2c9d924e3c..46d6b35ed597 100644 --- a/packages/proto-plus/proto/version.py +++ b/packages/proto-plus/proto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.26.0" +__version__ = "1.26.1" From bb8a341fc2f9ab1ca40f6d110d91af5210092802 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 10 Mar 2025 12:02:23 -0400 Subject: [PATCH 268/272] build: remove unused scripts (#537) * build: remove unused scripts * disable owlbot --------- Co-authored-by: ohmayr --- packages/proto-plus/.github/.OwlBot.lock.yaml | 17 - packages/proto-plus/.github/.OwlBot.yaml | 18 - .../proto-plus/.kokoro/docker/docs/Dockerfile | 89 ---- .../.kokoro/docker/docs/requirements.in | 2 - .../.kokoro/docker/docs/requirements.txt | 297 ----------- packages/proto-plus/.kokoro/docs/common.cfg | 67 --- .../.kokoro/docs/docs-presubmit.cfg | 28 - packages/proto-plus/.kokoro/docs/docs.cfg | 1 - .../proto-plus/.kokoro/populate-secrets.sh | 43 -- packages/proto-plus/.kokoro/publish-docs.sh | 58 --- packages/proto-plus/.kokoro/trampoline.sh | 28 - packages/proto-plus/.kokoro/trampoline_v2.sh | 487 ------------------ packages/proto-plus/.trampolinerc | 61 --- packages/proto-plus/owlbot.py | 36 -- 14 files changed, 1232 deletions(-) delete mode 100644 packages/proto-plus/.github/.OwlBot.lock.yaml delete mode 100644 packages/proto-plus/.github/.OwlBot.yaml delete mode 100644 packages/proto-plus/.kokoro/docker/docs/Dockerfile delete mode 100644 packages/proto-plus/.kokoro/docker/docs/requirements.in delete mode 100644 packages/proto-plus/.kokoro/docker/docs/requirements.txt delete mode 100644 packages/proto-plus/.kokoro/docs/common.cfg delete mode 100644 packages/proto-plus/.kokoro/docs/docs-presubmit.cfg delete mode 100644 packages/proto-plus/.kokoro/docs/docs.cfg delete mode 100755 packages/proto-plus/.kokoro/populate-secrets.sh delete mode 100755 packages/proto-plus/.kokoro/publish-docs.sh delete mode 100755 packages/proto-plus/.kokoro/trampoline.sh delete mode 100755 packages/proto-plus/.kokoro/trampoline_v2.sh delete mode 100644 packages/proto-plus/.trampolinerc delete mode 100644 packages/proto-plus/owlbot.py diff --git a/packages/proto-plus/.github/.OwlBot.lock.yaml b/packages/proto-plus/.github/.OwlBot.lock.yaml deleted file mode 100644 index 4c0027ff1c61..000000000000 --- a/packages/proto-plus/.github/.OwlBot.lock.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -docker: - image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:04c35dc5f49f0f503a306397d6d043685f8d2bb822ab515818c4208d7fb2db3a -# created: 2025-01-16T15:24:11.364245182Z diff --git a/packages/proto-plus/.github/.OwlBot.yaml b/packages/proto-plus/.github/.OwlBot.yaml deleted file mode 100644 index 0879a14a4334..000000000000 --- a/packages/proto-plus/.github/.OwlBot.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -docker: - image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - -begin-after-commit-hash: 6acf4a0a797f1082027985c55c4b14b60f673dd7 diff --git a/packages/proto-plus/.kokoro/docker/docs/Dockerfile b/packages/proto-plus/.kokoro/docker/docs/Dockerfile deleted file mode 100644 index e5410e296bd8..000000000000 --- a/packages/proto-plus/.kokoro/docker/docs/Dockerfile +++ /dev/null @@ -1,89 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ubuntu:24.04 - -ENV DEBIAN_FRONTEND noninteractive - -# Ensure local Python is preferred over distribution Python. -ENV PATH /usr/local/bin:$PATH - -# Install dependencies. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - apt-transport-https \ - build-essential \ - ca-certificates \ - curl \ - dirmngr \ - git \ - gpg-agent \ - graphviz \ - libbz2-dev \ - libdb5.3-dev \ - libexpat1-dev \ - libffi-dev \ - liblzma-dev \ - libreadline-dev \ - libsnappy-dev \ - libssl-dev \ - libsqlite3-dev \ - portaudio19-dev \ - redis-server \ - software-properties-common \ - ssh \ - sudo \ - tcl \ - tcl-dev \ - tk \ - tk-dev \ - uuid-dev \ - wget \ - zlib1g-dev \ - && add-apt-repository universe \ - && apt-get update \ - && apt-get -y install jq \ - && apt-get clean autoclean \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -f /var/cache/apt/archives/*.deb - - -###################### Install python 3.10.14 for docs/docfx session - -# Download python 3.10.14 -RUN wget https://www.python.org/ftp/python/3.10.14/Python-3.10.14.tgz - -# Extract files -RUN tar -xvf Python-3.10.14.tgz - -# Install python 3.10.14 -RUN ./Python-3.10.14/configure --enable-optimizations -RUN make altinstall - -ENV PATH /usr/local/bin/python3.10:$PATH - -###################### Install pip -RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ - && python3.10 /tmp/get-pip.py \ - && rm /tmp/get-pip.py - -# Test pip -RUN python3.10 -m pip - -# Install build requirements -COPY requirements.txt /requirements.txt -RUN python3.10 -m pip install --require-hashes -r requirements.txt - -CMD ["python3.10"] diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.in b/packages/proto-plus/.kokoro/docker/docs/requirements.in deleted file mode 100644 index 586bd07037ae..000000000000 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -nox -gcp-docuploader diff --git a/packages/proto-plus/.kokoro/docker/docs/requirements.txt b/packages/proto-plus/.kokoro/docker/docs/requirements.txt deleted file mode 100644 index a9360a25b707..000000000000 --- a/packages/proto-plus/.kokoro/docker/docs/requirements.txt +++ /dev/null @@ -1,297 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --generate-hashes requirements.in -# -argcomplete==3.5.3 \ - --hash=sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61 \ - --hash=sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392 - # via nox -cachetools==5.5.0 \ - --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ - --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a - # via google-auth -certifi==2024.12.14 \ - --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \ - --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db - # via requests -charset-normalizer==3.4.1 \ - --hash=sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537 \ - --hash=sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa \ - --hash=sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a \ - --hash=sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294 \ - --hash=sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b \ - --hash=sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd \ - --hash=sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601 \ - --hash=sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd \ - --hash=sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4 \ - --hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \ - --hash=sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2 \ - --hash=sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313 \ - --hash=sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd \ - --hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \ - --hash=sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8 \ - --hash=sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1 \ - --hash=sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2 \ - --hash=sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496 \ - --hash=sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d \ - --hash=sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b \ - --hash=sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e \ - --hash=sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a \ - --hash=sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4 \ - --hash=sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca \ - --hash=sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78 \ - --hash=sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408 \ - --hash=sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5 \ - --hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \ - --hash=sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f \ - --hash=sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a \ - --hash=sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765 \ - --hash=sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6 \ - --hash=sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146 \ - --hash=sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6 \ - --hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \ - --hash=sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd \ - --hash=sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c \ - --hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \ - --hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \ - --hash=sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176 \ - --hash=sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770 \ - --hash=sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824 \ - --hash=sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f \ - --hash=sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf \ - --hash=sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487 \ - --hash=sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d \ - --hash=sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd \ - --hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \ - --hash=sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534 \ - --hash=sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f \ - --hash=sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b \ - --hash=sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9 \ - --hash=sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd \ - --hash=sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125 \ - --hash=sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9 \ - --hash=sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de \ - --hash=sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11 \ - --hash=sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d \ - --hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \ - --hash=sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f \ - --hash=sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda \ - --hash=sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7 \ - --hash=sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a \ - --hash=sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971 \ - --hash=sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8 \ - --hash=sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41 \ - --hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \ - --hash=sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f \ - --hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \ - --hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \ - --hash=sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886 \ - --hash=sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77 \ - --hash=sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76 \ - --hash=sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247 \ - --hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \ - --hash=sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb \ - --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \ - --hash=sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e \ - --hash=sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6 \ - --hash=sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037 \ - --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \ - --hash=sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e \ - --hash=sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807 \ - --hash=sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407 \ - --hash=sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c \ - --hash=sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12 \ - --hash=sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3 \ - --hash=sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089 \ - --hash=sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd \ - --hash=sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e \ - --hash=sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 \ - --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616 - # via requests -click==8.1.8 \ - --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ - --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a - # via gcp-docuploader -colorlog==6.9.0 \ - --hash=sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff \ - --hash=sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2 - # via - # gcp-docuploader - # nox -distlib==0.3.9 \ - --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ - --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 - # via virtualenv -filelock==3.16.1 \ - --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ - --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 - # via virtualenv -gcp-docuploader==0.6.5 \ - --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ - --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea - # via -r requirements.in -google-api-core==2.24.0 \ - --hash=sha256:10d82ac0fca69c82a25b3efdeefccf6f28e02ebb97925a8cce8edbfe379929d9 \ - --hash=sha256:e255640547a597a4da010876d333208ddac417d60add22b6851a0c66a831fcaf - # via - # google-cloud-core - # google-cloud-storage -google-auth==2.37.0 \ - --hash=sha256:0054623abf1f9c83492c63d3f47e77f0a544caa3d40b2d98e099a611c2dd5d00 \ - --hash=sha256:42664f18290a6be591be5329a96fe30184be1a1badb7292a7f686a9659de9ca0 - # via - # google-api-core - # google-cloud-core - # google-cloud-storage -google-cloud-core==2.4.1 \ - --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ - --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 - # via google-cloud-storage -google-cloud-storage==2.19.0 \ - --hash=sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba \ - --hash=sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2 - # via gcp-docuploader -google-crc32c==1.6.0 \ - --hash=sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24 \ - --hash=sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d \ - --hash=sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e \ - --hash=sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57 \ - --hash=sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2 \ - --hash=sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8 \ - --hash=sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc \ - --hash=sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42 \ - --hash=sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f \ - --hash=sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa \ - --hash=sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b \ - --hash=sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc \ - --hash=sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760 \ - --hash=sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d \ - --hash=sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7 \ - --hash=sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d \ - --hash=sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0 \ - --hash=sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3 \ - --hash=sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3 \ - --hash=sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00 \ - --hash=sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871 \ - --hash=sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c \ - --hash=sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9 \ - --hash=sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205 \ - --hash=sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc \ - --hash=sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d \ - --hash=sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4 - # via - # google-cloud-storage - # google-resumable-media -google-resumable-media==2.7.2 \ - --hash=sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa \ - --hash=sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0 - # via google-cloud-storage -googleapis-common-protos==1.66.0 \ - --hash=sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c \ - --hash=sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed - # via google-api-core -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 - # via requests -nox==2024.10.9 \ - --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ - --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 - # via -r requirements.in -packaging==24.2 \ - --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ - --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via nox -platformdirs==4.3.6 \ - --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ - --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb - # via virtualenv -proto-plus==1.25.0 \ - --hash=sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961 \ - --hash=sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91 - # via google-api-core -protobuf==5.29.3 \ - --hash=sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f \ - --hash=sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7 \ - --hash=sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888 \ - --hash=sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620 \ - --hash=sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da \ - --hash=sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252 \ - --hash=sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a \ - --hash=sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e \ - --hash=sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107 \ - --hash=sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f \ - --hash=sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84 - # via - # gcp-docuploader - # google-api-core - # googleapis-common-protos - # proto-plus -pyasn1==0.6.1 \ - --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ - --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.4.1 \ - --hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \ - --hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c - # via google-auth -requests==2.32.3 \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 - # via - # google-api-core - # google-cloud-storage -rsa==4.9 \ - --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ - --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 - # via google-auth -six==1.17.0 \ - --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ - --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 - # via gcp-docuploader -tomli==2.2.1 \ - --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ - --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ - --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ - --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ - --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ - --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ - --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ - --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ - --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ - --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ - --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ - --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ - --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ - --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ - --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ - --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ - --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ - --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ - --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ - --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ - --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ - --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ - --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ - --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ - --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ - --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ - --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ - --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ - --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ - --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ - --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ - --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 - # via nox -urllib3==2.3.0 \ - --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ - --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d - # via requests -virtualenv==20.28.1 \ - --hash=sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb \ - --hash=sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329 - # via nox diff --git a/packages/proto-plus/.kokoro/docs/common.cfg b/packages/proto-plus/.kokoro/docs/common.cfg deleted file mode 100644 index 41c589d98039..000000000000 --- a/packages/proto-plus/.kokoro/docs/common.cfg +++ /dev/null @@ -1,67 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "proto-plus-python/.kokoro/trampoline_v2.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-lib-docs" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/proto-plus-python/.kokoro/publish-docs.sh" -} - -env_vars: { - key: "STAGING_BUCKET" - value: "docs-staging" -} - -env_vars: { - key: "V2_STAGING_BUCKET" - # Push non-cloud library docs to `docs-staging-v2-dev` instead of the - # Cloud RAD bucket `docs-staging-v2` - value: "docs-staging-v2-dev" -} - -# It will upload the docker image after successful builds. -env_vars: { - key: "TRAMPOLINE_IMAGE_UPLOAD" - value: "true" -} - -# It will always build the docker image. -env_vars: { - key: "TRAMPOLINE_DOCKERFILE" - value: ".kokoro/docker/docs/Dockerfile" -} - -# Fetch the token needed for reporting release status to GitHub -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "yoshi-automation-github-key" - } - } -} - -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "docuploader_service_account" - } - } -} diff --git a/packages/proto-plus/.kokoro/docs/docs-presubmit.cfg b/packages/proto-plus/.kokoro/docs/docs-presubmit.cfg deleted file mode 100644 index f0da5bff5212..000000000000 --- a/packages/proto-plus/.kokoro/docs/docs-presubmit.cfg +++ /dev/null @@ -1,28 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "STAGING_BUCKET" - value: "gcloud-python-test" -} - -env_vars: { - key: "V2_STAGING_BUCKET" - value: "gcloud-python-test" -} - -# We only upload the image in the main `docs` build. -env_vars: { - key: "TRAMPOLINE_IMAGE_UPLOAD" - value: "false" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/proto-plus-python/.kokoro/build.sh" -} - -# Only run this nox session. -env_vars: { - key: "NOX_SESSION" - value: "docs docfx" -} diff --git a/packages/proto-plus/.kokoro/docs/docs.cfg b/packages/proto-plus/.kokoro/docs/docs.cfg deleted file mode 100644 index 8f43917d92fe..000000000000 --- a/packages/proto-plus/.kokoro/docs/docs.cfg +++ /dev/null @@ -1 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/proto-plus/.kokoro/populate-secrets.sh b/packages/proto-plus/.kokoro/populate-secrets.sh deleted file mode 100755 index c435402f473e..000000000000 --- a/packages/proto-plus/.kokoro/populate-secrets.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} -function msg { println "$*" >&2 ;} -function println { printf '%s\n' "$(now) $*" ;} - - -# Populates requested secrets set in SECRET_MANAGER_KEYS from service account: -# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com -SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" -msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" -mkdir -p ${SECRET_LOCATION} -for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") -do - msg "Retrieving secret ${key}" - docker run --entrypoint=gcloud \ - --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \ - gcr.io/google.com/cloudsdktool/cloud-sdk \ - secrets versions access latest \ - --project cloud-devrel-kokoro-resources \ - --secret ${key} > \ - "${SECRET_LOCATION}/${key}" - if [[ $? == 0 ]]; then - msg "Secret written to ${SECRET_LOCATION}/${key}" - else - msg "Error retrieving secret ${key}" - fi -done diff --git a/packages/proto-plus/.kokoro/publish-docs.sh b/packages/proto-plus/.kokoro/publish-docs.sh deleted file mode 100755 index 4ed4aaf1346f..000000000000 --- a/packages/proto-plus/.kokoro/publish-docs.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -export PATH="${HOME}/.local/bin:${PATH}" - -# build docs -nox -s docs - -# create metadata -python3.10 -m docuploader create-metadata \ - --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3.10 setup.py --version) \ - --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3.10 setup.py --name) \ - --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ - --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ - --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) - -cat docs.metadata - -# upload docs -python3.10 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" - - -# docfx yaml files -nox -s docfx - -# create metadata. -python3.10 -m docuploader create-metadata \ - --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3.10 setup.py --version) \ - --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3.10 setup.py --name) \ - --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ - --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ - --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) - -cat docs.metadata - -# upload docs -python3.10 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" diff --git a/packages/proto-plus/.kokoro/trampoline.sh b/packages/proto-plus/.kokoro/trampoline.sh deleted file mode 100755 index 48f79699706e..000000000000 --- a/packages/proto-plus/.kokoro/trampoline.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -# Always run the cleanup script, regardless of the success of bouncing into -# the container. -function cleanup() { - chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh - ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh - echo "cleanup"; -} -trap cleanup EXIT - -$(dirname $0)/populate-secrets.sh # Secret Manager secrets. -python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" \ No newline at end of file diff --git a/packages/proto-plus/.kokoro/trampoline_v2.sh b/packages/proto-plus/.kokoro/trampoline_v2.sh deleted file mode 100755 index 35fa529231dc..000000000000 --- a/packages/proto-plus/.kokoro/trampoline_v2.sh +++ /dev/null @@ -1,487 +0,0 @@ -#!/usr/bin/env bash -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# trampoline_v2.sh -# -# This script does 3 things. -# -# 1. Prepare the Docker image for the test -# 2. Run the Docker with appropriate flags to run the test -# 3. Upload the newly built Docker image -# -# in a way that is somewhat compatible with trampoline_v1. -# -# To run this script, first download few files from gcs to /dev/shm. -# (/dev/shm is passed into the container as KOKORO_GFILE_DIR). -# -# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/secrets_viewer_service_account.json /dev/shm -# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/automl_secrets.txt /dev/shm -# -# Then run the script. -# .kokoro/trampoline_v2.sh -# -# These environment variables are required: -# TRAMPOLINE_IMAGE: The docker image to use. -# TRAMPOLINE_DOCKERFILE: The location of the Dockerfile. -# -# You can optionally change these environment variables: -# TRAMPOLINE_IMAGE_UPLOAD: -# (true|false): Whether to upload the Docker image after the -# successful builds. -# TRAMPOLINE_BUILD_FILE: The script to run in the docker container. -# TRAMPOLINE_WORKSPACE: The workspace path in the docker container. -# Defaults to /workspace. -# Potentially there are some repo specific envvars in .trampolinerc in -# the project root. - - -set -euo pipefail - -TRAMPOLINE_VERSION="2.0.5" - -if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then - readonly IO_COLOR_RED="$(tput setaf 1)" - readonly IO_COLOR_GREEN="$(tput setaf 2)" - readonly IO_COLOR_YELLOW="$(tput setaf 3)" - readonly IO_COLOR_RESET="$(tput sgr0)" -else - readonly IO_COLOR_RED="" - readonly IO_COLOR_GREEN="" - readonly IO_COLOR_YELLOW="" - readonly IO_COLOR_RESET="" -fi - -function function_exists { - [ $(LC_ALL=C type -t $1)"" == "function" ] -} - -# Logs a message using the given color. The first argument must be one -# of the IO_COLOR_* variables defined above, such as -# "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the -# given color. The log message will also have an RFC-3339 timestamp -# prepended (in UTC). You can disable the color output by setting -# TERM=vt100. -function log_impl() { - local color="$1" - shift - local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" - echo "================================================================" - echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}" - echo "================================================================" -} - -# Logs the given message with normal coloring and a timestamp. -function log() { - log_impl "${IO_COLOR_RESET}" "$@" -} - -# Logs the given message in green with a timestamp. -function log_green() { - log_impl "${IO_COLOR_GREEN}" "$@" -} - -# Logs the given message in yellow with a timestamp. -function log_yellow() { - log_impl "${IO_COLOR_YELLOW}" "$@" -} - -# Logs the given message in red with a timestamp. -function log_red() { - log_impl "${IO_COLOR_RED}" "$@" -} - -readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX) -readonly tmphome="${tmpdir}/h" -mkdir -p "${tmphome}" - -function cleanup() { - rm -rf "${tmpdir}" -} -trap cleanup EXIT - -RUNNING_IN_CI="${RUNNING_IN_CI:-false}" - -# The workspace in the container, defaults to /workspace. -TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}" - -pass_down_envvars=( - # TRAMPOLINE_V2 variables. - # Tells scripts whether they are running as part of CI or not. - "RUNNING_IN_CI" - # Indicates which CI system we're in. - "TRAMPOLINE_CI" - # Indicates the version of the script. - "TRAMPOLINE_VERSION" -) - -log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}" - -# Detect which CI systems we're in. If we're in any of the CI systems -# we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be -# the name of the CI system. Both envvars will be passing down to the -# container for telling which CI system we're in. -if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then - # descriptive env var for indicating it's on CI. - RUNNING_IN_CI="true" - TRAMPOLINE_CI="kokoro" - if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then - if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then - log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting." - exit 1 - fi - # This service account will be activated later. - TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" - else - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - gcloud auth list - fi - log_yellow "Configuring Container Registry access" - gcloud auth configure-docker --quiet - fi - pass_down_envvars+=( - # KOKORO dynamic variables. - "KOKORO_BUILD_NUMBER" - "KOKORO_BUILD_ID" - "KOKORO_JOB_NAME" - "KOKORO_GIT_COMMIT" - "KOKORO_GITHUB_COMMIT" - "KOKORO_GITHUB_PULL_REQUEST_NUMBER" - "KOKORO_GITHUB_PULL_REQUEST_COMMIT" - # For FlakyBot - "KOKORO_GITHUB_COMMIT_URL" - "KOKORO_GITHUB_PULL_REQUEST_URL" - ) -elif [[ "${TRAVIS:-}" == "true" ]]; then - RUNNING_IN_CI="true" - TRAMPOLINE_CI="travis" - pass_down_envvars+=( - "TRAVIS_BRANCH" - "TRAVIS_BUILD_ID" - "TRAVIS_BUILD_NUMBER" - "TRAVIS_BUILD_WEB_URL" - "TRAVIS_COMMIT" - "TRAVIS_COMMIT_MESSAGE" - "TRAVIS_COMMIT_RANGE" - "TRAVIS_JOB_NAME" - "TRAVIS_JOB_NUMBER" - "TRAVIS_JOB_WEB_URL" - "TRAVIS_PULL_REQUEST" - "TRAVIS_PULL_REQUEST_BRANCH" - "TRAVIS_PULL_REQUEST_SHA" - "TRAVIS_PULL_REQUEST_SLUG" - "TRAVIS_REPO_SLUG" - "TRAVIS_SECURE_ENV_VARS" - "TRAVIS_TAG" - ) -elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then - RUNNING_IN_CI="true" - TRAMPOLINE_CI="github-workflow" - pass_down_envvars+=( - "GITHUB_WORKFLOW" - "GITHUB_RUN_ID" - "GITHUB_RUN_NUMBER" - "GITHUB_ACTION" - "GITHUB_ACTIONS" - "GITHUB_ACTOR" - "GITHUB_REPOSITORY" - "GITHUB_EVENT_NAME" - "GITHUB_EVENT_PATH" - "GITHUB_SHA" - "GITHUB_REF" - "GITHUB_HEAD_REF" - "GITHUB_BASE_REF" - ) -elif [[ "${CIRCLECI:-}" == "true" ]]; then - RUNNING_IN_CI="true" - TRAMPOLINE_CI="circleci" - pass_down_envvars+=( - "CIRCLE_BRANCH" - "CIRCLE_BUILD_NUM" - "CIRCLE_BUILD_URL" - "CIRCLE_COMPARE_URL" - "CIRCLE_JOB" - "CIRCLE_NODE_INDEX" - "CIRCLE_NODE_TOTAL" - "CIRCLE_PREVIOUS_BUILD_NUM" - "CIRCLE_PROJECT_REPONAME" - "CIRCLE_PROJECT_USERNAME" - "CIRCLE_REPOSITORY_URL" - "CIRCLE_SHA1" - "CIRCLE_STAGE" - "CIRCLE_USERNAME" - "CIRCLE_WORKFLOW_ID" - "CIRCLE_WORKFLOW_JOB_ID" - "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" - "CIRCLE_WORKFLOW_WORKSPACE_ID" - ) -fi - -# Configure the service account for pulling the docker image. -function repo_root() { - local dir="$1" - while [[ ! -d "${dir}/.git" ]]; do - dir="$(dirname "$dir")" - done - echo "${dir}" -} - -# Detect the project root. In CI builds, we assume the script is in -# the git tree and traverse from there, otherwise, traverse from `pwd` -# to find `.git` directory. -if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then - PROGRAM_PATH="$(realpath "$0")" - PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" - PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")" -else - PROJECT_ROOT="$(repo_root $(pwd))" -fi - -log_yellow "Changing to the project root: ${PROJECT_ROOT}." -cd "${PROJECT_ROOT}" - -# To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need -# to use this environment variable in `PROJECT_ROOT`. -if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then - - mkdir -p "${tmpdir}/gcloud" - gcloud_config_dir="${tmpdir}/gcloud" - - log_yellow "Using isolated gcloud config: ${gcloud_config_dir}." - export CLOUDSDK_CONFIG="${gcloud_config_dir}" - - log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication." - gcloud auth activate-service-account \ - --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}" - log_yellow "Configuring Container Registry access" - gcloud auth configure-docker --quiet -fi - -required_envvars=( - # The basic trampoline configurations. - "TRAMPOLINE_IMAGE" - "TRAMPOLINE_BUILD_FILE" -) - -if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then - source "${PROJECT_ROOT}/.trampolinerc" -fi - -log_yellow "Checking environment variables." -for e in "${required_envvars[@]}" -do - if [[ -z "${!e:-}" ]]; then - log "Missing ${e} env var. Aborting." - exit 1 - fi -done - -# We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1 -# script: e.g. "github/repo-name/.kokoro/run_tests.sh" -TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}" -log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}" - -# ignore error on docker operations and test execution -set +e - -log_yellow "Preparing Docker image." -# We only download the docker image in CI builds. -if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then - # Download the docker image specified by `TRAMPOLINE_IMAGE` - - # We may want to add --max-concurrent-downloads flag. - - log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}." - if docker pull "${TRAMPOLINE_IMAGE}"; then - log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}." - has_image="true" - else - log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}." - has_image="false" - fi -else - # For local run, check if we have the image. - if docker images "${TRAMPOLINE_IMAGE}:latest" | grep "${TRAMPOLINE_IMAGE}"; then - has_image="true" - else - has_image="false" - fi -fi - - -# The default user for a Docker container has uid 0 (root). To avoid -# creating root-owned files in the build directory we tell docker to -# use the current user ID. -user_uid="$(id -u)" -user_gid="$(id -g)" -user_name="$(id -un)" - -# To allow docker in docker, we add the user to the docker group in -# the host os. -docker_gid=$(cut -d: -f3 < <(getent group docker)) - -update_cache="false" -if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then - # Build the Docker image from the source. - context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}") - docker_build_flags=( - "-f" "${TRAMPOLINE_DOCKERFILE}" - "-t" "${TRAMPOLINE_IMAGE}" - "--build-arg" "UID=${user_uid}" - "--build-arg" "USERNAME=${user_name}" - ) - if [[ "${has_image}" == "true" ]]; then - docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}") - fi - - log_yellow "Start building the docker image." - if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then - echo "docker build" "${docker_build_flags[@]}" "${context_dir}" - fi - - # ON CI systems, we want to suppress docker build logs, only - # output the logs when it fails. - if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then - if docker build "${docker_build_flags[@]}" "${context_dir}" \ - > "${tmpdir}/docker_build.log" 2>&1; then - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - cat "${tmpdir}/docker_build.log" - fi - - log_green "Finished building the docker image." - update_cache="true" - else - log_red "Failed to build the Docker image, aborting." - log_yellow "Dumping the build logs:" - cat "${tmpdir}/docker_build.log" - exit 1 - fi - else - if docker build "${docker_build_flags[@]}" "${context_dir}"; then - log_green "Finished building the docker image." - update_cache="true" - else - log_red "Failed to build the Docker image, aborting." - exit 1 - fi - fi -else - if [[ "${has_image}" != "true" ]]; then - log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting." - exit 1 - fi -fi - -# We use an array for the flags so they are easier to document. -docker_flags=( - # Remove the container after it exists. - "--rm" - - # Use the host network. - "--network=host" - - # Run in priviledged mode. We are not using docker for sandboxing or - # isolation, just for packaging our dev tools. - "--privileged" - - # Run the docker script with the user id. Because the docker image gets to - # write in ${PWD} you typically want this to be your user id. - # To allow docker in docker, we need to use docker gid on the host. - "--user" "${user_uid}:${docker_gid}" - - # Pass down the USER. - "--env" "USER=${user_name}" - - # Mount the project directory inside the Docker container. - "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}" - "--workdir" "${TRAMPOLINE_WORKSPACE}" - "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}" - - # Mount the temporary home directory. - "--volume" "${tmphome}:/h" - "--env" "HOME=/h" - - # Allow docker in docker. - "--volume" "/var/run/docker.sock:/var/run/docker.sock" - - # Mount the /tmp so that docker in docker can mount the files - # there correctly. - "--volume" "/tmp:/tmp" - # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR - # TODO(tmatsuo): This part is not portable. - "--env" "TRAMPOLINE_SECRET_DIR=/secrets" - "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile" - "--env" "KOKORO_GFILE_DIR=/secrets/gfile" - "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore" - "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore" -) - -# Add an option for nicer output if the build gets a tty. -if [[ -t 0 ]]; then - docker_flags+=("-it") -fi - -# Passing down env vars -for e in "${pass_down_envvars[@]}" -do - if [[ -n "${!e:-}" ]]; then - docker_flags+=("--env" "${e}=${!e}") - fi -done - -# If arguments are given, all arguments will become the commands run -# in the container, otherwise run TRAMPOLINE_BUILD_FILE. -if [[ $# -ge 1 ]]; then - log_yellow "Running the given commands '" "${@:1}" "' in the container." - readonly commands=("${@:1}") - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" - fi - docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" -else - log_yellow "Running the tests in a Docker container." - docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}") - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" - fi - docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" -fi - - -test_retval=$? - -if [[ ${test_retval} -eq 0 ]]; then - log_green "Build finished with ${test_retval}" -else - log_red "Build finished with ${test_retval}" -fi - -# Only upload it when the test passes. -if [[ "${update_cache}" == "true" ]] && \ - [[ $test_retval == 0 ]] && \ - [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then - log_yellow "Uploading the Docker image." - if docker push "${TRAMPOLINE_IMAGE}"; then - log_green "Finished uploading the Docker image." - else - log_red "Failed uploading the Docker image." - fi - # Call trampoline_after_upload_hook if it's defined. - if function_exists trampoline_after_upload_hook; then - trampoline_after_upload_hook - fi - -fi - -exit "${test_retval}" diff --git a/packages/proto-plus/.trampolinerc b/packages/proto-plus/.trampolinerc deleted file mode 100644 index 0080152373d5..000000000000 --- a/packages/proto-plus/.trampolinerc +++ /dev/null @@ -1,61 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Add required env vars here. -required_envvars+=( -) - -# Add env vars which are passed down into the container here. -pass_down_envvars+=( - "NOX_SESSION" - ############### - # Docs builds - ############### - "STAGING_BUCKET" - "V2_STAGING_BUCKET" - ################## - # Samples builds - ################## - "INSTALL_LIBRARY_FROM_SOURCE" - "RUN_TESTS_SESSION" - "BUILD_SPECIFIC_GCLOUD_PROJECT" - # Target directories. - "RUN_TESTS_DIRS" - # The nox session to run. - "RUN_TESTS_SESSION" -) - -# Prevent unintentional override on the default image. -if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ - [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then - echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." - exit 1 -fi - -# Define the default value if it makes sense. -if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then - TRAMPOLINE_IMAGE_UPLOAD="" -fi - -if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then - TRAMPOLINE_IMAGE="" -fi - -if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then - TRAMPOLINE_DOCKERFILE="" -fi - -if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then - TRAMPOLINE_BUILD_FILE="" -fi diff --git a/packages/proto-plus/owlbot.py b/packages/proto-plus/owlbot.py deleted file mode 100644 index 3600950cf8a0..000000000000 --- a/packages/proto-plus/owlbot.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This script is used to synthesize generated parts of this library.""" - -import pathlib - -import synthtool as s -from synthtool import gcp -from synthtool.languages import python - -REPO_ROOT = pathlib.Path(__file__).parent.absolute() - -common = gcp.CommonTemplates() -templated_files = common.py_library() - -excludes = [ - "continuous/**", - "presubmit/**", - "samples/**", - "build.sh", - "test-samples*.sh", -] -s.move(templated_files / ".kokoro", excludes=excludes) -s.move(templated_files / ".trampolinerc") From 31375e031c9e6c97fe70c17ad83aaf13ffe69e30 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 18 Nov 2025 21:45:26 -0500 Subject: [PATCH 269/272] tests: filter float_precision deprecation warning (#548) Without this PR, tests fail because warnings are treated as errors. See https://github.com/googleapis/proto-plus-python/issues/547 for follow up work related to the warning. ``` ________________________________________________________________________________________________________________________ test_serialize_to_dict_float_precision __________________________________________________________________________________________________________________________ def test_serialize_to_dict_float_precision(): class Squid(proto.Message): mass_kg = proto.Field(proto.FLOAT, number=1) s = Squid(mass_kg=3.14159265) > s_dict = Squid.to_dict(s, float_precision=3) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ tests/test_message.py:336: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ proto/message.py:610: in to_dict return MessageToDict( .nox/unit-3-13-implementation-upb/lib/python3.13/site-packages/google/protobuf/json_format.py:158: in MessageToDict printer = _Printer( _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = , preserving_proto_field_name = True, use_integers_for_enums = True, descriptor_pool = None, float_precision = 3, always_print_fields_with_no_presence = True def __init__( self, preserving_proto_field_name=False, use_integers_for_enums=False, descriptor_pool=None, float_precision=None, always_print_fields_with_no_presence=False, ): self.always_print_fields_with_no_presence = ( always_print_fields_with_no_presence ) self.preserving_proto_field_name = preserving_proto_field_name self.use_integers_for_enums = use_integers_for_enums self.descriptor_pool = descriptor_pool if float_precision: > warnings.warn( 'float_precision option is deprecated for json_format. ' 'This will turn into error in 7.34.0, please remove it ' 'before that.' ) E UserWarning: float_precision option is deprecated for json_format. This will turn into error in 7.34.0, please remove it before that. .nox/unit-3-13-implementation-upb/lib/python3.13/site-packages/google/protobuf/json_format.py:195: UserWarning ``` --- packages/proto-plus/pytest.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/proto-plus/pytest.ini b/packages/proto-plus/pytest.ini index ba89114a1d71..4f8fe4d2a91b 100644 --- a/packages/proto-plus/pytest.ini +++ b/packages/proto-plus/pytest.ini @@ -6,3 +6,6 @@ filterwarnings = 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 + # Remove once deprecated field `float_precision` is removed + # See https://github.com/googleapis/proto-plus-python/issues/547 + ignore:float_precision option is deprecated for json_format:UserWarning From 05490366d439ee22305f7a6db18fbda9da2401f8 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 18 Nov 2025 22:20:09 -0500 Subject: [PATCH 270/272] chore(librarian): onboard to librarian (#549) Towards https://github.com/googleapis/librarian/issues/2451 --- .../proto-plus/.github/release-please.yml | 2 - .../proto-plus/.github/release-trigger.yml | 2 - .../.github/sync-repo-settings.yaml | 46 ------------------- packages/proto-plus/.librarian/state.yaml | 10 ++++ packages/proto-plus/CHANGELOG.md | 4 ++ 5 files changed, 14 insertions(+), 50 deletions(-) delete mode 100644 packages/proto-plus/.github/release-please.yml delete mode 100644 packages/proto-plus/.github/release-trigger.yml delete mode 100644 packages/proto-plus/.github/sync-repo-settings.yaml create mode 100644 packages/proto-plus/.librarian/state.yaml diff --git a/packages/proto-plus/.github/release-please.yml b/packages/proto-plus/.github/release-please.yml deleted file mode 100644 index 466597e5b196..000000000000 --- a/packages/proto-plus/.github/release-please.yml +++ /dev/null @@ -1,2 +0,0 @@ -releaseType: python -handleGHRelease: true diff --git a/packages/proto-plus/.github/release-trigger.yml b/packages/proto-plus/.github/release-trigger.yml deleted file mode 100644 index 1bc7271817db..000000000000 --- a/packages/proto-plus/.github/release-trigger.yml +++ /dev/null @@ -1,2 +0,0 @@ -enabled: true -multiScmName: proto-plus-python diff --git a/packages/proto-plus/.github/sync-repo-settings.yaml b/packages/proto-plus/.github/sync-repo-settings.yaml deleted file mode 100644 index e0e29f0876e9..000000000000 --- a/packages/proto-plus/.github/sync-repo-settings.yaml +++ /dev/null @@ -1,46 +0,0 @@ -rebaseMergeAllowed: false -squashMergeAllowed: true -mergeCommitAllowed: false -branchProtectionRules: -- pattern: main - isAdminEnforced: true - requiredStatusCheckContexts: - - 'style-check' - - 'docs' - - 'lint' - - 'unit (3.7, cpp)' - - 'unit (3.7, python)' - - 'unit (3.7, upb)' - - 'unit (3.8, cpp)' - - 'unit (3.8, python)' - - 'unit (3.8, upb)' - - 'unit (3.9, cpp)' - - 'unit (3.9, python)' - - 'unit (3.9, upb)' - - 'unit (3.10, cpp)' - - 'unit (3.10, python)' - - 'unit (3.10, upb)' - - 'unit (pypy3.10, python)' - - 'unit (3.11, python)' - - 'unit (3.11, upb)' - - 'unit (3.12, python)' - - 'unit (3.12, upb)' - - 'unit (3.13, python)' - - 'unit (3.13, upb)' - - 'unit (3.14, python)' - - 'unit (3.14, upb)' - - 'prerelease (3.14, python)' - - 'prerelease (3.14, upb)' - - cover - - OwlBot Post Processor - - 'cla/google' - requiredApprovingReviewCount: 1 - requiresCodeOwnerReviews: true - requiresStrictStatusChecks: true -permissionRules: - - team: yoshi-python - permission: push - - team: actools - permission: admin - - team: actools-python - permission: push diff --git a/packages/proto-plus/.librarian/state.yaml b/packages/proto-plus/.librarian/state.yaml new file mode 100644 index 000000000000..7705ed2d30f0 --- /dev/null +++ b/packages/proto-plus/.librarian/state.yaml @@ -0,0 +1,10 @@ +image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:8e2c32496077054105bd06c54a59d6a6694287bc053588e24debe6da6920ad91 +libraries: + - id: proto-plus + version: 1.26.1 + apis: [] + source_roots: + - . + preserve_regex: [] + remove_regex: [] + tag_format: v{version} diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index c0dea7c12f4b..0bc4e9acd440 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +[PyPI History][1] + +[1]: https://pypi.org/project/proto-plus/#history + ## [1.26.1](https://github.com/googleapis/proto-plus-python/compare/v1.26.0...v1.26.1) (2025-03-05) From d7acd0bcbe2398173203e98a9d5cc03354a658dc Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 24 Nov 2025 22:12:19 +0000 Subject: [PATCH 271/272] Trigger owlbot post-processor --- owl-bot-staging/proto-plus/proto-plus/proto-plus.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 owl-bot-staging/proto-plus/proto-plus/proto-plus.txt diff --git a/owl-bot-staging/proto-plus/proto-plus/proto-plus.txt b/owl-bot-staging/proto-plus/proto-plus/proto-plus.txt new file mode 100644 index 000000000000..e69de29bb2d1 From add54c419a057585b80ff9a5b7bab1f432091d8a Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 24 Nov 2025 22:12:26 +0000 Subject: [PATCH 272/272] build: proto-plus migration: adjust owlbot-related files --- packages/proto-plus/.github/CODEOWNERS | 7 - .../proto-plus/.github/workflows/lint.yml | 22 --- .../proto-plus/.github/workflows/tests.yml | 142 ------------------ 3 files changed, 171 deletions(-) delete mode 100644 packages/proto-plus/.github/CODEOWNERS delete mode 100644 packages/proto-plus/.github/workflows/lint.yml delete mode 100644 packages/proto-plus/.github/workflows/tests.yml diff --git a/packages/proto-plus/.github/CODEOWNERS b/packages/proto-plus/.github/CODEOWNERS deleted file mode 100644 index 6f558ad8337f..000000000000 --- a/packages/proto-plus/.github/CODEOWNERS +++ /dev/null @@ -1,7 +0,0 @@ -# Code owners file. -# This file controls who is tagged for review for any given pull request. - -# For syntax help see: -# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax - -* @googleapis/python-core-client-libraries diff --git a/packages/proto-plus/.github/workflows/lint.yml b/packages/proto-plus/.github/workflows/lint.yml deleted file mode 100644 index 08c2b5d05fca..000000000000 --- a/packages/proto-plus/.github/workflows/lint.yml +++ /dev/null @@ -1,22 +0,0 @@ -on: - pull_request: - branches: - - main -name: lint -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install nox - run: | - python -m pip install --upgrade setuptools pip wheel - python -m pip install nox - - name: Run lint_setup_py - run: | - nox -s lint_setup_py diff --git a/packages/proto-plus/.github/workflows/tests.yml b/packages/proto-plus/.github/workflows/tests.yml deleted file mode 100644 index 77bfc9ad7d4b..000000000000 --- a/packages/proto-plus/.github/workflows/tests.yml +++ /dev/null @@ -1,142 +0,0 @@ -name: Tests - -# Controls when the action will run. -on: - pull_request: - push: - branches: [ $default-branch ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -concurrency: - group: tests-${{ github.head_ref }} - cancel-in-progress: true - -jobs: - style-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.8 - uses: actions/setup-python@v5 - with: - python-version: 3.8 - - name: Install black - run: pip install black==22.3.0 - - name: Check diff - run: black --diff --check . - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install nox. - run: python -m pip install nox - - name: Build the documentation. - run: nox -s docs - unit: - runs-on: ubuntu-22.04 - strategy: - matrix: - # See https://github.com/actions/setup-python?tab=readme-ov-file#basic-usage - # for the format of the entries in 'python'. - # See https://downloads.python.org/pypy/ for the current supported versions of PyPy. - python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy3.10'] - 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 - - variant: "cpp" - python: 3.14 - # In PyPy 3.10, we see the following warning - # UserWarning: Selected implementation upb is not available. Falling back to the python implementation. - - variant: "upb" - python: 'pypy3.10' - # In PyPy 3.10, we see the following warning - # UserWarning: PyPy does not work yet with cpp protocol buffers. Falling back to the python implementation. - - variant: "cpp" - python: 'pypy3.10' - 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-${{ matrix.variant }}-${{ matrix.python }} - run: | - nox -s "unit-${{ matrix.python }}(implementation='${{ matrix.variant }}')" - - name: Upload coverage results - uses: actions/upload-artifact@v4 - with: - name: coverage-artifact-${{ matrix.variant }}-${{ matrix.python }} - path: .coverage-${{ matrix.variant }}-${{ matrix.python }} - include-hidden-files: true - prerelease: - runs-on: ubuntu-22.04 - strategy: - matrix: - python: ['3.14'] - 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: - - unit - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install coverage - run: | - python -m pip install --upgrade setuptools pip wheel - python -m pip install coverage - - name: Download coverage results - uses: actions/download-artifact@v4 - with: - path: .coverage-results/ - - name: Report coverage results - run: | - find .coverage-results -type f -name '*.zip' -exec unzip {} \; - coverage combine .coverage-results/**/.coverage* - coverage report --show-missing --fail-under=100