pax_global_header 0000666 0000000 0000000 00000000064 14634565734 0014533 g ustar 00root root 0000000 0000000 52 comment=a235f5b55b175b13f683206a71c4fd119b59c827
proto-plus-python-1.24.0/ 0000775 0000000 0000000 00000000000 14634565734 0015262 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.coveragerc 0000664 0000000 0000000 00000000314 14634565734 0017401 0 ustar 00root root 0000000 0000000 [run]
branch = True
[report]
show_missing = True
omit =
proto/marshal/compat.py
exclude_lines =
# Re-enable the standard pragma
pragma: NO COVER
# Ignore debug-only repr
def __repr__
proto-plus-python-1.24.0/.flake8 0000664 0000000 0000000 00000000425 14634565734 0016436 0 ustar 00root root 0000000 0000000 [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
proto-plus-python-1.24.0/.github/ 0000775 0000000 0000000 00000000000 14634565734 0016622 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.github/.OwlBot.lock.yaml 0000664 0000000 0000000 00000001406 14634565734 0021722 0 ustar 00root root 0000000 0000000 # 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.
docker:
image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest
digest: sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070
# created: 2024-04-12T11:35:58.922854369Z
proto-plus-python-1.24.0/.github/.OwlBot.yaml 0000664 0000000 0000000 00000001315 14634565734 0020772 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/.github/CODEOWNERS 0000664 0000000 0000000 00000000426 14634565734 0020217 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/.github/release-please.yml 0000664 0000000 0000000 00000000052 14634565734 0022231 0 ustar 00root root 0000000 0000000 releaseType: python
handleGHRelease: true
proto-plus-python-1.24.0/.github/release-trigger.yml 0000664 0000000 0000000 00000000017 14634565734 0022424 0 ustar 00root root 0000000 0000000 enabled: true
proto-plus-python-1.24.0/.github/sync-repo-settings.yaml 0000664 0000000 0000000 00000001732 14634565734 0023266 0 ustar 00root root 0000000 0000000 rebaseMergeAllowed: false
squashMergeAllowed: true
mergeCommitAllowed: false
branchProtectionRules:
- pattern: main
isAdminEnforced: true
requiredStatusCheckContexts:
- 'style-check'
- 'docs'
- '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 (3.11, python)'
- 'unit (3.11, upb)'
- 'unit (3.12, python)'
- 'unit (3.12, upb)'
- 'prerelease (3.12, python)'
- 'prerelease (3.12, 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
proto-plus-python-1.24.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14634565734 0020657 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.github/workflows/tests.yml 0000664 0000000 0000000 00000010372 14634565734 0022547 0 ustar 00root root 0000000 0000000 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.9
uses: actions/setup-python@v5
with:
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-22.04
strategy:
matrix:
python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
variant: ['cpp', 'python', 'upb']
# TODO(https://github.com/googleapis/proto-plus-python/issues/389):
# Remove the 'cpp' implementation once support for Protobuf 3.x is dropped.
# The 'cpp' implementation requires Protobuf == 3.x however version 3.x
# does not support Python 3.11 and newer. The 'cpp' implementation
# must be excluded from the test matrix for these runtimes.
exclude:
- variant: "cpp"
python: 3.11
- variant: "cpp"
python: 3.12
- variant: "cpp"
python: 3.13
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
- 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 }}
run: |
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-22.04
strategy:
matrix:
python: ['3.12']
variant: ['python', 'upb']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
- name: Install nox
run: |
pip install nox
- name: Run unit tests
env:
COVERAGE_FILE: .coverage-prerelease-${{ matrix.variant }}
run: |
nox -s "prerelease_deps(implementation='${{ matrix.variant }}')"
- name: Upload coverage results
uses: actions/upload-artifact@v4
with:
name: coverage-artifact-prerelease-${{ matrix.variant }}
path: .coverage-prerelease-${{ matrix.variant }}
cover:
runs-on: ubuntu-latest
needs:
- 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
proto-plus-python-1.24.0/.gitignore 0000664 0000000 0000000 00000001164 14634565734 0017254 0 ustar 00root root 0000000 0000000 *.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
proto-plus-python-1.24.0/.kokoro/ 0000775 0000000 0000000 00000000000 14634565734 0016644 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.kokoro/docker/ 0000775 0000000 0000000 00000000000 14634565734 0020113 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.kokoro/docker/docs/ 0000775 0000000 0000000 00000000000 14634565734 0021043 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.kokoro/docker/docs/Dockerfile 0000664 0000000 0000000 00000004210 14634565734 0023032 0 ustar 00root root 0000000 0000000 # 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.
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
# Install build requirements
COPY requirements.txt /requirements.txt
RUN python3 -m pip install --require-hashes -r requirements.txt
CMD ["python3.8"]
proto-plus-python-1.24.0/.kokoro/docker/docs/requirements.in 0000664 0000000 0000000 00000000004 14634565734 0024110 0 ustar 00root root 0000000 0000000 nox
proto-plus-python-1.24.0/.kokoro/docker/docs/requirements.txt 0000664 0000000 0000000 00000003415 14634565734 0024332 0 ustar 00root root 0000000 0000000 #
# 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
proto-plus-python-1.24.0/.kokoro/docs/ 0000775 0000000 0000000 00000000000 14634565734 0017574 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.kokoro/docs/common.cfg 0000664 0000000 0000000 00000002752 14634565734 0021553 0 ustar 00root root 0000000 0000000 # 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"
}
}
} proto-plus-python-1.24.0/.kokoro/docs/docs-presubmit.cfg 0000664 0000000 0000000 00000001006 14634565734 0023212 0 ustar 00root root 0000000 0000000 # 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"
}
proto-plus-python-1.24.0/.kokoro/docs/docs.cfg 0000664 0000000 0000000 00000000064 14634565734 0021205 0 ustar 00root root 0000000 0000000 # Format: //devtools/kokoro/config/proto/build.proto proto-plus-python-1.24.0/.kokoro/populate-secrets.sh 0000775 0000000 0000000 00000003020 14634565734 0022475 0 ustar 00root root 0000000 0000000 #!/bin/bash
# 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.
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
proto-plus-python-1.24.0/.kokoro/publish-docs.sh 0000775 0000000 0000000 00000004442 14634565734 0021603 0 ustar 00root root 0000000 0000000 #!/bin/bash
# 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
#
# 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}"
proto-plus-python-1.24.0/.kokoro/release.sh 0000775 0000000 0000000 00000002256 14634565734 0020630 0 ustar 00root root 0000000 0000000 #!/bin/bash
# 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
#
# 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/*
proto-plus-python-1.24.0/.kokoro/release/ 0000775 0000000 0000000 00000000000 14634565734 0020264 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/.kokoro/release/common.cfg 0000664 0000000 0000000 00000002436 14634565734 0022242 0 ustar 00root root 0000000 0000000 # 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"
}
# 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"
}
}
proto-plus-python-1.24.0/.kokoro/release/release.cfg 0000664 0000000 0000000 00000000064 14634565734 0022365 0 ustar 00root root 0000000 0000000 # Format: //devtools/kokoro/config/proto/build.proto proto-plus-python-1.24.0/.kokoro/requirements.in 0000664 0000000 0000000 00000000400 14634565734 0021711 0 ustar 00root root 0000000 0000000 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
proto-plus-python-1.24.0/.kokoro/requirements.txt 0000664 0000000 0000000 00000077651 14634565734 0022150 0 ustar 00root root 0000000 0000000 #
# 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.1.4 \
--hash=sha256:72558ba729e4c468572609817226fb0a6e7e9a0a7d477b882be168c0b4a62b94 \
--hash=sha256:fbe56f8cda08aa9a04b307d8482ea703e96a6a801611acb4be9bf3942017989f
# via nox
attrs==23.1.0 \
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
# via gcp-releasetool
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.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 \
--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
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 \
--hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \
--hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8
# via virtualenv
docutils==0.20.1 \
--hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \
--hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b
# via readme-renderer
filelock==3.13.1 \
--hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \
--hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c
# 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
# via -r requirements.in
google-api-core==2.12.0 \
--hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \
--hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160
# via
# google-cloud-core
# google-cloud-storage
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.3 \
--hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \
--hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863
# via google-cloud-storage
google-cloud-storage==2.13.0 \
--hash=sha256:ab0bf2e1780a1b74cf17fccb13788070b729f50c252f0c94ada2aae0ca95437d \
--hash=sha256:f62dc4c7b6cd4360d072e3deb28035fbdad491ac3d9b0b1815a12daea10f37c7
# 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-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.61.0 \
--hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \
--hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b
# 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
# via
# -r requirements.in
# keyring
# twine
jaraco-classes==3.3.0 \
--hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \
--hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621
# via keyring
jeepney==0.8.0 \
--hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \
--hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755
# via
# keyring
# secretstorage
jinja2==3.1.3 \
--hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \
--hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90
# via gcp-releasetool
keyring==24.2.0 \
--hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \
--hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509
# via
# gcp-releasetool
# twine
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
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
# via readme-renderer
nox==2023.4.22 \
--hash=sha256:0b1adc619c58ab4fa57d6ab2e7823fe47a32e70202f287d78474adcc7bda1891 \
--hash=sha256:46c0560b0dc609d7d967dc99e22cb463d3c4caf54a5fda735d6c11b5177e3a9f
# via -r requirements.in
packaging==23.2 \
--hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
# via
# gcp-releasetool
# nox
pkginfo==1.9.6 \
--hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \
--hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046
# via twine
platformdirs==3.11.0 \
--hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \
--hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e
# 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
# via
# gcp-docuploader
# gcp-releasetool
# google-api-core
# googleapis-common-protos
pyasn1==0.5.0 \
--hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \
--hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde
# via
# pyasn1-modules
# rsa
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.16.1 \
--hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
--hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
# via
# readme-renderer
# rich
pyjwt==2.8.0 \
--hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \
--hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320
# via gcp-releasetool
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==42.0 \
--hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \
--hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1
# 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==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.6.0 \
--hash=sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245 \
--hash=sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef
# 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
twine==4.0.2 \
--hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \
--hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8
# via -r requirements.in
typing-extensions==4.8.0 \
--hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \
--hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef
# via -r requirements.in
urllib3==2.0.7 \
--hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \
--hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e
# via
# requests
# twine
virtualenv==20.24.6 \
--hash=sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af \
--hash=sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381
# via nox
wheel==0.41.3 \
--hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \
--hash=sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841
# via -r requirements.in
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==69.2.0 \
--hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \
--hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c
# via -r requirements.in
proto-plus-python-1.24.0/.kokoro/trampoline.sh 0000775 0000000 0000000 00000001710 14634565734 0021354 0 ustar 00root root 0000000 0000000 #!/bin/bash
# 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.
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" proto-plus-python-1.24.0/.kokoro/trampoline_v2.sh 0000775 0000000 0000000 00000035165 14634565734 0021776 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
# 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.
# 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}"
proto-plus-python-1.24.0/.readthedocs.yml 0000664 0000000 0000000 00000000106 14634565734 0020345 0 ustar 00root root 0000000 0000000 ---
build:
image: latest
python:
pip_install: true
version: 3.9
proto-plus-python-1.24.0/.repo-metadata.json 0000664 0000000 0000000 00000000552 14634565734 0020760 0 ustar 00root root 0000000 0000000 {
"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",
"default_version": "",
"codeowner_team": ""
}
proto-plus-python-1.24.0/.trampolinerc 0000664 0000000 0000000 00000003252 14634565734 0017764 0 ustar 00root root 0000000 0000000 # 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.
# 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
proto-plus-python-1.24.0/CHANGELOG.md 0000664 0000000 0000000 00000064630 14634565734 0017104 0 ustar 00root root 0000000 0000000 # 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)
### 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)
### 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)
### 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)
### 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
* 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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-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.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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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)
### 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))
proto-plus-python-1.24.0/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000003675 14634565734 0020074 0 ustar 00root root 0000000 0000000 # 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/)
proto-plus-python-1.24.0/CONTRIBUTING.md 0000664 0000000 0000000 00000002111 14634565734 0017506 0 ustar 00root root 0000000 0000000 # 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/).
proto-plus-python-1.24.0/CONTRIBUTING.rst 0000664 0000000 0000000 00000001515 14634565734 0017725 0 ustar 00root root 0000000 0000000 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.
proto-plus-python-1.24.0/LICENSE 0000664 0000000 0000000 00000026136 14634565734 0016277 0 ustar 00root root 0000000 0000000
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.
proto-plus-python-1.24.0/MANIFEST.in 0000664 0000000 0000000 00000001301 14634565734 0017013 0 ustar 00root root 0000000 0000000 # -*- 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__ proto-plus-python-1.24.0/README.rst 0000664 0000000 0000000 00000001753 14634565734 0016757 0 ustar 00root root 0000000 0000000 Proto Plus for Python
=====================
|pypi| |release level| |docs|
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/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
:target: https://proto-plus-python.readthedocs.io/en/latest/
proto-plus-python-1.24.0/SECURITY.md 0000664 0000000 0000000 00000000511 14634565734 0017050 0 ustar 00root root 0000000 0000000 # 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.
proto-plus-python-1.24.0/docs/ 0000775 0000000 0000000 00000000000 14634565734 0016212 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/docs/Makefile 0000664 0000000 0000000 00000001161 14634565734 0017651 0 ustar 00root root 0000000 0000000 # 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)
proto-plus-python-1.24.0/docs/conf.py 0000664 0000000 0000000 00000012371 14634565734 0017515 0 ustar 00root root 0000000 0000000 # -*- 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
import proto
sys.path.insert(0, os.path.abspath(".."))
# -- Project information -----------------------------------------------------
project = "Proto Plus for Python"
copyright = "2018, Google LLC"
author = "Google LLC"
version = proto.version.__version__
# -- 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 = "en"
# 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}
proto-plus-python-1.24.0/docs/fields.rst 0000664 0000000 0000000 00000017007 14634565734 0020217 0 ustar 00root root 0000000 0000000 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)
.. 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
----------
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.
.. 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
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
---------------
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
.. _PEP-468: https://www.python.org/dev/peps/pep-0468/
proto-plus-python-1.24.0/docs/index.rst 0000664 0000000 0000000 00000001476 14634565734 0020063 0 ustar 00root root 0000000 0000000 Proto Plus for Python
=====================
Beautiful, Pythonic protocol buffers.
This library provides a clean, readable, straightforward pattern for
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.
.. _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.
.. _protobuf: https://pypi.org/project/protobuf/
Table of Contents
-----------------
.. toctree::
:maxdepth: 2
messages
fields
marshal
status
reference/index
proto-plus-python-1.24.0/docs/marshal.rst 0000664 0000000 0000000 00000006526 14634565734 0020404 0 ustar 00root root 0000000 0000000 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.ListValue`` ``MutableSequence`` Yes
``google.protobuf.StringValue`` ``str`` Yes
``google.protobuf.Struct`` ``MutableMapping`` Yes
``google.protobuf.Timestamp`` ``datetime.datetime`` Yes
``google.protobuf.UInt32Value`` ``int`` Yes
``google.protobuf.UInt64Value`` ``int`` Yes
``google.protobuf.Value`` JSON-encodable values 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. 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,
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
-------------
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.
proto-plus-python-1.24.0/docs/messages.rst 0000664 0000000 0000000 00000021465 14634565734 0020563 0 ustar 00root root 0000000 0000000 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).
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.
* 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
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'
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 reference 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
-----
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
-------------
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)
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)
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)
.. note::
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:
.. 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))
]
proto-plus-python-1.24.0/docs/reference/ 0000775 0000000 0000000 00000000000 14634565734 0020150 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/docs/reference/datetime_helpers.rst 0000664 0000000 0000000 00000000130 14634565734 0024212 0 ustar 00root root 0000000 0000000 Datetime Helpers
----------------
.. automodule:: proto.datetime_helpers
:members:
proto-plus-python-1.24.0/docs/reference/index.rst 0000664 0000000 0000000 00000000771 14634565734 0022016 0 ustar 00root root 0000000 0000000 Reference
---------
Below is a reference for the major classes and functions within this
module.
- 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
proto-plus-python-1.24.0/docs/reference/marshal.rst 0000664 0000000 0000000 00000000075 14634565734 0022333 0 ustar 00root root 0000000 0000000 Marshal
-------
.. automodule:: proto.marshal
:members:
proto-plus-python-1.24.0/docs/reference/message.rst 0000664 0000000 0000000 00000000617 14634565734 0022332 0 ustar 00root root 0000000 0000000 Message and Field
-----------------
.. autoclass:: proto.message.Message
:members:
.. automethod:: pb
.. automethod:: wrap
.. automethod:: serialize
.. automethod:: deserialize
.. automethod:: to_json
.. automethod:: from_json
.. automethod:: to_dict
.. automethod:: copy_from
.. automodule:: proto.fields
:members:
.. automodule:: proto.enums
:members:
proto-plus-python-1.24.0/docs/status.rst 0000664 0000000 0000000 00000000614 14634565734 0020270 0 ustar 00root root 0000000 0000000 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
-------------
- Specialized behavior for ``google.protobuf.FieldMask`` objects.
proto-plus-python-1.24.0/noxfile.py 0000664 0000000 0000000 00000012176 14634565734 0017307 0 ustar 00root root 0000000 0000000 # 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 nox
import pathlib
CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
PYTHON_VERSIONS = [
"3.7",
"3.8",
"3.9",
"3.10",
"3.11",
"3.12",
"3.13",
]
# Error if a python version is missing
nox.options.error_on_missing_interpreters = True
@nox.session(python=PYTHON_VERSIONS)
@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"] = implementation
session.install("coverage", "pytest", "pytest-cov", "pytz")
session.install("-e", ".[testing]", "-c", constraints_path)
# 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/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",
]
),
)
# 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
# Install test environment dependencies
session.install("coverage", "pytest", "pytest-cov", "pytz")
# Install the package without dependencies
session.install("-e", ".", "--no-deps")
prerel_deps = [
"google-api-core",
# dependency of google-api-core
"googleapis-common-protos",
]
for dep in prerel_deps:
session.install("--pre", "--no-deps", "--upgrade", dep)
session.install("--pre", "--upgrade", "protobuf")
# Print out prerelease package versions
session.run(
"python", "-c", "import google.protobuf; print(google.protobuf.__version__)"
)
session.run(
"python", "-c", "import google.api_core; print(google.api_core.__version__)"
)
# TODO(https://github.com/googleapis/proto-plus-python/issues/403): re-enable `-W=error`
# The warnings-as-errors flag `-W=error` was removed in
# https://github.com/googleapis/proto-plus-python/pull/400.
# It should be re-added once issue
# https://github.com/protocolbuffers/protobuf/issues/15077 is fixed.
session.run(
"pytest",
"--quiet",
*(
session.posargs # Coverage info when running individual tests is annoying.
or [
"--cov=proto",
"--cov-config=.coveragerc",
"--cov-report=term",
"--cov-report=html",
"tests",
]
),
)
@nox.session(python="3.9")
def docs(session):
"""Build the docs."""
session.install(
# 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!
session.run("rm", "-rf", "docs/_build/")
session.run(
"sphinx-build",
"-W",
"-b",
"html",
"-d",
"docs/_build/doctrees",
"docs/",
"docs/_build/html/",
)
proto-plus-python-1.24.0/owlbot.py 0000664 0000000 0000000 00000002100 14634565734 0017133 0 ustar 00root root 0000000 0000000 # 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")
proto-plus-python-1.24.0/proto/ 0000775 0000000 0000000 00000000000 14634565734 0016425 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/proto/__init__.py 0000664 0000000 0000000 00000003260 14634565734 0020537 0 ustar 00root root 0000000 0000000 # 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 .enums import Enum
from .fields import Field
from .fields import MapField
from .fields import RepeatedField
from .marshal import Marshal
from .message import Message
from .modules import define_module as module
from .primitives import ProtoType
from .version import __version__
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__ = (
"__version__",
"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",
)
proto-plus-python-1.24.0/proto/_file_info.py 0000664 0000000 0000000 00000017352 14634565734 0021100 0 ustar 00root root 0000000 0000000 # 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 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
from proto.marshal.rules.message import MessageRule
log = logging.getLogger("_FileInfo")
class _FileInfo(
collections.namedtuple(
"_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__"):
return frozenset(module.__protobuf__.manifest)
return frozenset()
def _get_remaining_manifest(self, new_class):
return self._get_manifest(new_class) - {new_class.__name__}
def _calculate_salt(self, new_class, fallback):
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 {class_name} is not in it".format(
module=inspect.getmodule(new_class).__name__,
class_name=new_class.__name__,
)
)
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.
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).
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.
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.
#
# 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, 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]
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.
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) 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 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
# __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 = self._get_remaining_manifest(new_class)
# We are ready if all members have been populated.
return all(hasattr(module, i) for i in manifest)
@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)) or (
field.enum and isinstance(field.enum, str)
):
yield field
proto-plus-python-1.24.0/proto/_package_info.py 0000664 0000000 0000000 00000003562 14634565734 0021552 0 ustar 00root root 0000000 0000000 # 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):
"""Return the package and marshal to use.
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[str, ~.Marshal]:
- 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__"))
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.
# 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.
return (package, marshal)
proto-plus-python-1.24.0/proto/datetime_helpers.py 0000664 0000000 0000000 00000016313 14634565734 0022321 0 ustar 00root root 0000000 0000000 # 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.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
_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 formatted 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 arguments.
"""
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,
)
proto-plus-python-1.24.0/proto/enums.py 0000664 0000000 0000000 00000013011 14634565734 0020122 0 ustar 00root root 0000000 0000000 # 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 google.protobuf import descriptor_pb2
from proto import _file_info
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)
# 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())
)
# 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:
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(
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,
),
options=opts,
)
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
class Enum(enum.IntEnum, metaclass=ProtoEnumMeta):
"""A enum object that also builds a protobuf enum descriptor."""
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
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:
def __init__(self, *, full_name: str, pb):
self.full_name = full_name
self.pb = pb
proto-plus-python-1.24.0/proto/fields.py 0000664 0000000 0000000 00000012374 14634565734 0020254 0 ustar 00root root 0000000 0000000 # 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 enum import EnumMeta
from google.protobuf import descriptor_pb2
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from proto.primitives import ProtoType
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,
*,
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 = None
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):
# Note: We only support the "shortcut syntax" for enums
# when receiving the actual class.
if isinstance(proto_type, (EnumMeta, EnumTypeWrapper)):
enum = proto_type
proto_type = ProtoType.ENUM
else:
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.json_name = json_name
self.optional = optional
self.oneof = oneof
# 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:
type_name = (
self.message.DESCRIPTOR.full_name
if hasattr(self.message, "DESCRIPTOR")
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.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=self.proto_type,
type_name=type_name,
json_name=self.json_name,
proto3_optional=self.optional,
)
# 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 the primitive type if a primitive."""
# For enums, return the Python enum.
if self.enum:
return self.enum
# For primitive fields, we still want to know
# what the type is.
if not self.message:
return self.proto_type
# 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."""
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",
)
proto-plus-python-1.24.0/proto/marshal/ 0000775 0000000 0000000 00000000000 14634565734 0020054 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/proto/marshal/__init__.py 0000664 0000000 0000000 00000001166 14634565734 0022171 0 ustar 00root root 0000000 0000000 # 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 .marshal import Marshal
__all__ = ("Marshal",)
proto-plus-python-1.24.0/proto/marshal/collections/ 0000775 0000000 0000000 00000000000 14634565734 0022372 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/proto/marshal/collections/__init__.py 0000664 0000000 0000000 00000001363 14634565734 0024506 0 ustar 00root root 0000000 0000000 # 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 .maps import MapComposite
from .repeated import Repeated
from .repeated import RepeatedComposite
__all__ = (
"MapComposite",
"Repeated",
"RepeatedComposite",
)
proto-plus-python-1.24.0/proto/marshal/collections/maps.py 0000664 0000000 0000000 00000005551 14634565734 0023712 0 ustar 00root root 0000000 0000000 # 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
from google.protobuf.message import Message
class MapComposite(collections.abc.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
proto-plus-python-1.24.0/proto/marshal/collections/repeated.py 0000664 0000000 0000000 00000015166 14634565734 0024546 0 ustar 00root root 0000000 0000000 # 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.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, proto_type=None):
"""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
self._proto_type = proto_type
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])
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."""
# 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).
#
# 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, 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()
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])
def __setitem__(self, key, value):
# 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)
self.pb.insert(index, pb_value)
proto-plus-python-1.24.0/proto/marshal/compat.py 0000664 0000000 0000000 00000004571 14634565734 0021720 0 ustar 00root root 0000000 0000000 # 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
import google.protobuf
PROTOBUF_VERSION = google.protobuf.__version__
# Import protobuf 4.xx first and fallback to earlier version
# if not present.
try:
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,)
# 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,)
# 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",
)
proto-plus-python-1.24.0/proto/marshal/marshal.py 0000664 0000000 0000000 00000027376 14634565734 0022074 0 ustar 00root root 0000000 0000000 # 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 enum
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
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.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
from proto.marshal.rules import field_mask
from proto.primitives import ProtoType
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 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
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 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()
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 TimestampRule:
...
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
"""
# 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._rules[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._rules[proto_type] = rule_class()
return rule_class
return register_rule_class
def reset(self):
"""Reset the registry to its initial state."""
self._rules.clear()
# Register date and time wrappers.
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())
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())
# 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))
# 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 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.
value_type = type(value) # Minor performance boost over isinstance
if value_type in compat.repeated_composite_types:
return RepeatedComposite(value, marshal=self)
if value_type in compat.repeated_scalar_types:
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.
# 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)
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,
):
# 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
):
recursive_type = type(proto_type().value)
return {k: self.to_proto(recursive_type, v) for k, v in value.items()}
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):
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 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.
"""
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.
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
def to_proto(self, value):
return value
__all__ = ("Marshal",)
proto-plus-python-1.24.0/proto/marshal/rules/ 0000775 0000000 0000000 00000000000 14634565734 0021206 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/proto/marshal/rules/__init__.py 0000664 0000000 0000000 00000001077 14634565734 0023324 0 ustar 00root root 0000000 0000000 # 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.
proto-plus-python-1.24.0/proto/marshal/rules/bytes.py 0000664 0000000 0000000 00000003071 14634565734 0022707 0 ustar 00root root 0000000 0000000 # 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 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
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
proto-plus-python-1.24.0/proto/marshal/rules/dates.py 0000664 0000000 0000000 00000006077 14634565734 0022672 0 ustar 00root root 0000000 0000000 # 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
from proto import datetime_helpers, utils
class TimestampRule:
"""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_helpers.DatetimeWithNanoseconds:
if isinstance(value, timestamp_pb2.Timestamp):
if absent:
return None
return datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(value)
return value
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,
)
if isinstance(value, str):
timestamp_value = timestamp_pb2.Timestamp()
timestamp_value.FromJsonString(value=value)
return timestamp_value
return value
class DurationRule:
"""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,
)
if isinstance(value, str):
duration_value = duration_pb2.Duration()
duration_value.FromJsonString(value=value)
return duration_value
return value
proto-plus-python-1.24.0/proto/marshal/rules/enums.py 0000664 0000000 0000000 00000004245 14634565734 0022714 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/proto/marshal/rules/field_mask.py 0000664 0000000 0000000 00000002236 14634565734 0023661 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/proto/marshal/rules/message.py 0000664 0000000 0000000 00000003461 14634565734 0023210 0 ustar 00root root 0000000 0000000 # 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 MessageRule:
"""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:
# 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
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
proto-plus-python-1.24.0/proto/marshal/rules/stringy_numbers.py 0000664 0000000 0000000 00000003373 14634565734 0025020 0 ustar 00root root 0000000 0000000 # 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):
if value is not None:
return self._python_type(value)
return None
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,
]
proto-plus-python-1.24.0/proto/marshal/rules/struct.py 0000664 0000000 0000000 00000012152 14634565734 0023105 0 ustar 00root root 0000000 0000000 # 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 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" or absent:
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,
)
# 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."""
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 (
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.
# 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 (
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.
# 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
proto-plus-python-1.24.0/proto/marshal/rules/wrappers.py 0000664 0000000 0000000 00000004350 14634565734 0023425 0 ustar 00root root 0000000 0000000 # 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 WrapperRule:
"""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 DoubleValueRule(WrapperRule):
_proto_type = wrappers_pb2.DoubleValue
_python_type = float
class FloatValueRule(WrapperRule):
_proto_type = wrappers_pb2.FloatValue
_python_type = float
class Int64ValueRule(WrapperRule):
_proto_type = wrappers_pb2.Int64Value
_python_type = int
class UInt64ValueRule(WrapperRule):
_proto_type = wrappers_pb2.UInt64Value
_python_type = int
class Int32ValueRule(WrapperRule):
_proto_type = wrappers_pb2.Int32Value
_python_type = int
class UInt32ValueRule(WrapperRule):
_proto_type = wrappers_pb2.UInt32Value
_python_type = int
class BoolValueRule(WrapperRule):
_proto_type = wrappers_pb2.BoolValue
_python_type = bool
class StringValueRule(WrapperRule):
_proto_type = wrappers_pb2.StringValue
_python_type = str
class BytesValueRule(WrapperRule):
_proto_type = wrappers_pb2.BytesValue
_python_type = bytes
proto-plus-python-1.24.0/proto/message.py 0000664 0000000 0000000 00000116755 14634565734 0020442 0 ustar 00root root 0000000 0000000 # 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 re
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
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
from proto.marshal import Marshal
from proto.primitives import ProtoType
from proto.utils import has_upb
PROTOBUF_VERSION = google.protobuf.__version__
_upb = has_upb() # Important to cache result here.
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)
# Get the essential information about the proto package, and where
# 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(".")
# 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
map_fields = {}
for key, field in 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,
),
"_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,
)
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],
)
# 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.
fields = []
new_attrs = {}
oneofs = collections.OrderedDict()
proto_imports = set()
index = 0
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
# 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)
# Same thing, but for enums.
elif field.enum and not isinstance(field.enum, str):
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
# 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
if opt_attrs:
mcls = type("AttrsMeta", (mcls,), opt_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())
)
# 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)
# 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 = descriptor_pb2.MessageOptions(**new_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()
],
options=opts,
)
# If any descriptors were nested under this one, they need to be
# attached as nested types here.
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))
# 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:
file_info.descriptor.message_type.add().MergeFrom(desc)
else:
file_info.nested[local_path] = desc
# Create the MessageInfo instance to be attached to this message.
new_attrs["_meta"] = _MessageInfo(
fields=fields,
full_name=full_name,
marshal=marshal,
options=opts,
package=package,
)
# Run the superclass constructor.
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
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(new_class=cls, fallback_salt=full_name)
# Done; return the class.
return cls
@classmethod
def __prepare__(mcls, name, bases, **kwargs):
return collections.OrderedDict()
@property
def meta(cls):
return cls._meta
def __dir__(self):
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)
return names
except AttributeError:
return dir(type)
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):
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):
"""Return a Message object that shallowly wraps the descriptor.
Args:
pb: A protocol buffer object, such as would be returned by
:meth:`pb`.
"""
# Optimized fast path.
instance = cls.__new__(cls)
super(cls, instance).__setattr__("_pb", pb)
return instance
def serialize(cls, instance) -> bytes:
"""Return the serialized proto.
Args:
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, coerce=True).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.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=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
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.
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.
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.
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.
"""
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.
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, ignore_unknown_fields=ignore_unknown_fields)
return instance
def to_dict(
cls,
instance,
*,
use_integers_for_enums=True,
preserving_proto_field_name=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).
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)): 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.
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.
"""
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
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.
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,
*,
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.
super().__setattr__("_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
# passed in.
#
# The `wrap` method on the metaclass is the public API for taking
# ownership of the passed in protobuf object.
mapping = copy.deepcopy(mapping)
if kwargs:
mapping.MergeFrom(self._meta.pb(**kwargs))
super().__setattr__("_pb", mapping)
return
elif isinstance(mapping, type(self)):
# Just use the above logic on mapping's underlying pb.
self.__init__(mapping=mapping._pb, **kwargs)
return
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,
)
)
params = {}
# Update the mapping to address any values that need to be
# 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_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)
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()}
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())
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
accommodate 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.
"""
(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."""
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[0] == "_":
return super().__setattr__(key, value)
marshal = self._meta.marshal
(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.
# 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}))
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.
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.
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,
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.marshal = marshal
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
__all__ = ("Message",)
proto-plus-python-1.24.0/proto/modules.py 0000664 0000000 0000000 00000003061 14634565734 0020447 0 ustar 00root root 0000000 0000000 # 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 (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.
"""
if not marshal:
marshal = package
return _ProtoModule(
package=package,
marshal=marshal,
manifest=frozenset(manifest),
)
__all__ = ("define_module",)
proto-plus-python-1.24.0/proto/primitives.py 0000664 0000000 0000000 00000001750 14634565734 0021175 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/proto/utils.py 0000664 0000000 0000000 00000003163 14634565734 0020142 0 ustar 00root root 0000000 0000000 # 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 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.
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",)
proto-plus-python-1.24.0/proto/version.py 0000664 0000000 0000000 00000001127 14634565734 0020465 0 ustar 00root root 0000000 0000000 # 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.24.0"
proto-plus-python-1.24.0/pytest.ini 0000664 0000000 0000000 00000000611 14634565734 0017311 0 ustar 00root root 0000000 0000000 [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
# Remove once deprecated field `including_default_value_fields` is removed
ignore:.*The argument `including_default_value_fields` has been removed.*:DeprecationWarning
proto-plus-python-1.24.0/renovate.json 0000664 0000000 0000000 00000000253 14634565734 0020000 0 ustar 00root root 0000000 0000000 {
"extends": [
"config:base",
"group:all",
":disableDependencyDashboard",
"schedule:weekly"
],
"ignorePaths": [
".kokoro/requirements.txt"
]
}
proto-plus-python-1.24.0/setup.py 0000664 0000000 0000000 00000004567 14634565734 0017010 0 ustar 00root root 0000000 0000000 # 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 re
import os
from setuptools import find_namespace_packages, 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(exclude=["docs", "tests"]),
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",
"Topic :: Software Development :: Code Generators",
"Topic :: Software Development :: Libraries :: Python Modules",
],
zip_safe=False,
)
proto-plus-python-1.24.0/testing/ 0000775 0000000 0000000 00000000000 14634565734 0016737 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/testing/constraints-3.10.txt 0000664 0000000 0000000 00000000000 14634565734 0022414 0 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/testing/constraints-3.11.txt 0000664 0000000 0000000 00000000000 14634565734 0022415 0 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/testing/constraints-3.12.txt 0000664 0000000 0000000 00000000000 14634565734 0022416 0 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/testing/constraints-3.13.txt 0000664 0000000 0000000 00000000000 14634565734 0022417 0 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/testing/constraints-3.7.txt 0000664 0000000 0000000 00000000477 14634565734 0022364 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/testing/constraints-3.8.txt 0000664 0000000 0000000 00000000000 14634565734 0022343 0 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/testing/constraints-3.9.txt 0000664 0000000 0000000 00000000000 14634565734 0022344 0 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/tests/ 0000775 0000000 0000000 00000000000 14634565734 0016424 5 ustar 00root root 0000000 0000000 proto-plus-python-1.24.0/tests/clam.py 0000664 0000000 0000000 00000001661 14634565734 0017716 0 ustar 00root root 0000000 0000000 # 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)
proto-plus-python-1.24.0/tests/conftest.py 0000664 0000000 0000000 00000010743 14634565734 0020630 0 ustar 00root root 0000000 0000000 # 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 importlib
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
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):
_FileInfo.registry.clear()
# Replace the descriptor pool and symbol database to avoid tests
# polluting one another.
pool = type(descriptor_pool.Default())()
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),
]
if descriptor_pool._USE_C_DESCRIPTORS:
item._mocks.append(
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]
# 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)
# 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)
# 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.
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:
importlib.reload(rules.wrappers)
if "struct_pb2" in reloaded:
importlib.reload(rules.struct)
if reloaded.intersection({"timestamp_pb2", "duration_pb2"}):
importlib.reload(rules.dates)
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)
proto-plus-python-1.24.0/tests/enums_test.py 0000664 0000000 0000000 00000001506 14634565734 0021166 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/mollusc.py 0000664 0000000 0000000 00000001422 14634565734 0020453 0 ustar 00root root 0000000 0000000 # 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)
proto-plus-python-1.24.0/tests/test_datetime_helpers.py 0000664 0000000 0000000 00000022235 14634565734 0023357 0 ustar 00root root 0000000 0000000 # 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
@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 convert.
Returns:
int: Microseconds since the unix epoch.
"""
assert value.tzinfo is datetime.timezone.utc
return calendar.timegm(value.timetuple())
proto-plus-python-1.24.0/tests/test_enum_total_ordering.py 0000664 0000000 0000000 00000006363 14634565734 0024105 0 ustar 00root root 0000000 0000000 # 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 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):
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
def test_hashing():
to_hash = enums_test.OneEnum.SOME_VALUE
{to_hash: "testing"} # no raise
proto-plus-python-1.24.0/tests/test_fields_bytes.py 0000664 0000000 0000000 00000005417 14634565734 0022520 0 ustar 00root root 0000000 0000000 # 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 base64
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"
# 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
# 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
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
proto-plus-python-1.24.0/tests/test_fields_composite.py 0000664 0000000 0000000 00000005147 14634565734 0023374 0 ustar 00root root 0000000 0000000 # 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 == ""
proto-plus-python-1.24.0/tests/test_fields_composite_string_ref.py 0000664 0000000 0000000 00000006342 14634565734 0025614 0 ustar 00root root 0000000 0000000 # 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
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():
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))
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
proto-plus-python-1.24.0/tests/test_fields_enum.py 0000664 0000000 0000000 00000024644 14634565734 0022341 0 ustar 00root root 0000000 0000000 # 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 os
import proto
import pytest
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
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 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
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 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
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 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
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 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
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 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
foo = Foo(Foo.pb()(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_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
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 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
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 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
foo = Foo()
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 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
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(proto.Enum):
COLOR_UNSPECIFIED = 0
RED = 1
GREEN = 2
BLUE = 3
color = proto.Field(Color, number=1)
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(proto.Enum):
COLOR_UNSPECIFIED = 0
RED = 1
GREEN = 2
BLUE = 3
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 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
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
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
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
proto-plus-python-1.24.0/tests/test_fields_int.py 0000664 0000000 0000000 00000007540 14634565734 0022163 0 ustar 00root root 0000000 0000000 # 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
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
proto-plus-python-1.24.0/tests/test_fields_map_composite.py 0000664 0000000 0000000 00000005624 14634565734 0024231 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_fields_map_scalar.py 0000664 0000000 0000000 00000003404 14634565734 0023466 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_fields_mitigate_collision.py 0000664 0000000 0000000 00000005533 14634565734 0025247 0 ustar 00root root 0000000 0000000 # 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
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. 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
def test_fields_mitigate_collision():
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"
# 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"
proto-plus-python-1.24.0/tests/test_fields_oneof.py 0000664 0000000 0000000 00000003022 14634565734 0022466 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_fields_optional.py 0000664 0000000 0000000 00000004727 14634565734 0023222 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_fields_repeated_composite.py 0000664 0000000 0000000 00000020273 14634565734 0025242 0 ustar 00root root 0000000 0000000 # 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
from enum import Enum
import pytest
from google.protobuf import timestamp_pb2
import proto
from proto.datetime_helpers import DatetimeWithNanoseconds
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 == baz.foos
assert baz.foos[0].bar == 42
assert isinstance(baz.foos[0], Foo)
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)
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=[DatetimeWithNanoseconds(2012, 4, 21, 15, tzinfo=timezone.utc)]
)
foo.timestamps.append(timestamp_pb2.Timestamp(seconds=86400 * 365))
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
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_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)
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_index():
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
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_value_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))
proto-plus-python-1.24.0/tests/test_fields_repeated_scalar.py 0000664 0000000 0000000 00000006024 14634565734 0024503 0 ustar 00root root 0000000 0000000 # 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")
proto-plus-python-1.24.0/tests/test_fields_string.py 0000664 0000000 0000000 00000003150 14634565734 0022670 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_file_info_salting.py 0000664 0000000 0000000 00000004475 14634565734 0023522 0 ustar 00root root 0000000 0000000 # 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={},
nested_enum={},
),
)
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"
proto-plus-python-1.24.0/tests/test_file_info_salting_with_manifest.py 0000664 0000000 0000000 00000005027 14634565734 0026435 0 ustar 00root root 0000000 0000000 # 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={},
nested_enum={},
),
)
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"
proto-plus-python-1.24.0/tests/test_json.py 0000664 0000000 0000000 00000016014 14634565734 0021010 0 ustar 00root root 0000000 0000000 # 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 re
import proto
from google.protobuf.json_format import MessageToJson, Parse, ParseError
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_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)
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
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"}'
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"}'
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)
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
# 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)
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
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
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}'
proto-plus-python-1.24.0/tests/test_marshal_field_mask.py 0000664 0000000 0000000 00000005327 14634565734 0023651 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_marshal_register.py 0000664 0000000 0000000 00000002455 14634565734 0023376 0 ustar 00root root 0000000 0000000 # 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
from proto.marshal.marshal import BaseMarshal
def test_registration():
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(marshal._rules[empty_pb2.Empty], Rule)
def test_invalid_marshal_class():
marshal = BaseMarshal()
with pytest.raises(TypeError):
@marshal.register(empty_pb2.Empty)
class Marshal:
pass
def test_invalid_marshal_rule():
marshal = BaseMarshal()
with pytest.raises(TypeError):
marshal.register(empty_pb2.Empty, rule=object())
proto-plus-python-1.24.0/tests/test_marshal_strict.py 0000664 0000000 0000000 00000001421 14634565734 0023052 0 ustar 00root root 0000000 0000000 # 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)
proto-plus-python-1.24.0/tests/test_marshal_stringy_numbers.py 0000664 0000000 0000000 00000003623 14634565734 0025002 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_marshal_types_dates.py 0000664 0000000 0000000 00000022262 14634565734 0024074 0 ustar 00root root 0000000 0000000 # 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
from proto.marshal.marshal import BaseMarshal
from proto import datetime_helpers
from proto.datetime_helpers import DatetimeWithNanoseconds
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, DatetimeWithNanoseconds)
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=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
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()
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
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, 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_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(
proto.MESSAGE,
number=1,
message=timestamp_pb2.Timestamp,
)
foo = Foo()
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)
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=DatetimeWithNanoseconds(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_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(
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 = BaseMarshal()
py_value = DatetimeWithNanoseconds(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 = 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
proto-plus-python-1.24.0/tests/test_marshal_types_enum.py 0000664 0000000 0000000 00000005073 14634565734 0023741 0 ustar 00root root 0000000 0000000 # 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
__protobuf__ = proto.module(package="test.marshal.enum")
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")
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 dict(mc.bivalves) == {"clam": clam, "oyster": Bivalve.OYSTER}
proto-plus-python-1.24.0/tests/test_marshal_types_message.py 0000664 0000000 0000000 00000002370 14634565734 0024416 0 ustar 00root root 0000000 0000000 # 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.rules.message import MessageRule
def test_to_proto():
class Foo(proto.Message):
bar = proto.Field(proto.INT32, number=1)
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
def test_to_python():
class Foo(proto.Message):
bar = proto.Field(proto.INT32, number=1)
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
proto-plus-python-1.24.0/tests/test_marshal_types_struct.py 0000664 0000000 0000000 00000015037 14634565734 0024322 0 ustar 00root root 0000000 0000000 # 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_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)
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_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)
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 "value" not in foo
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}
proto-plus-python-1.24.0/tests/test_marshal_types_wrappers_bool.py 0000664 0000000 0000000 00000006525 14634565734 0025656 0 ustar 00root root 0000000 0000000 # 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
from proto.marshal.marshal import BaseMarshal
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 = 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
proto-plus-python-1.24.0/tests/test_message.py 0000664 0000000 0000000 00000033647 14634565734 0021476 0 ustar 00root root 0000000 0000000 # 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 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_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)
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())
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")
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
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"
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
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
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
# 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_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)
# 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")
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)))
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
def test_dir_message_base():
assert set(dir(proto.Message)) == set(dir(type))
proto-plus-python-1.24.0/tests/test_message_filename.py 0000664 0000000 0000000 00000001511 14634565734 0023317 0 ustar 00root root 0000000 0000000 # 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__default_package.foo.proto"
)
proto-plus-python-1.24.0/tests/test_message_filename_with_and_without_manifest.py 0000664 0000000 0000000 00000002706 14634565734 0030654 0 ustar 00root root 0000000 0000000 # 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"
)
proto-plus-python-1.24.0/tests/test_message_filename_with_manifest.py 0000664 0000000 0000000 00000002236 14634565734 0026245 0 ustar 00root root 0000000 0000000 # 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"
)
proto-plus-python-1.24.0/tests/test_message_nested.py 0000664 0000000 0000000 00000004017 14634565734 0023025 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_message_pickling.py 0000664 0000000 0000000 00000002522 14634565734 0023342 0 ustar 00root root 0000000 0000000 # 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
proto-plus-python-1.24.0/tests/test_modules.py 0000664 0000000 0000000 00000007732 14634565734 0021516 0 ustar 00root root 0000000 0000000 # 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 unittest import mock
import inspect
import sys
from google.protobuf import wrappers_pb2
import proto
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_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",
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
# 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]
proto-plus-python-1.24.0/tests/zone.py 0000664 0000000 0000000 00000001442 14634565734 0017752 0 ustar 00root root 0000000 0000000 # 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