| # Copyright 2016 Google LLC | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| from __future__ import absolute_import | |
| import os | |
| import pathlib | |
| import re | |
| import shutil | |
| import unittest | |
| # https://github.com/google/importlab/issues/25 | |
| import nox # pytype: disable=import-error | |
| BLACK_VERSION = "black==22.3.0" | |
| BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] | |
| # Black and flake8 clash on the syntax for ignoring flake8's F401 in this file. | |
| BLACK_EXCLUDES = ["--exclude", "^/google/api_core/operations_v1/__init__.py"] | |
| PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] | |
| DEFAULT_PYTHON_VERSION = "3.10" | |
| CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() | |
| # 'docfx' is excluded since it only needs to run in 'docs-presubmit' | |
| nox.options.sessions = [ | |
| "unit", | |
| "unit_grpc_gcp", | |
| "unit_wo_grpc", | |
| "unit_w_prerelease_deps", | |
| "unit_w_async_rest_extra", | |
| "cover", | |
| "pytype", | |
| "mypy", | |
| "lint", | |
| "lint_setup_py", | |
| "blacken", | |
| "docs", | |
| ] | |
| # Error if a python version is missing | |
| nox.options.error_on_missing_interpreters = True | |
| @nox.session(python=DEFAULT_PYTHON_VERSION) | |
| def lint(session): | |
| """Run linters. | |
| Returns a failure if the linters find linting errors or sufficiently | |
| serious code quality issues. | |
| """ | |
| session.install("flake8", BLACK_VERSION) | |
| session.install(".") | |
| session.run( | |
| "black", | |
| "--check", | |
| *BLACK_EXCLUDES, | |
| *BLACK_PATHS, | |
| ) | |
| session.run("flake8", "google", "tests") | |
| @nox.session(python=DEFAULT_PYTHON_VERSION) | |
| def blacken(session): | |
| """Run black. | |
| Format code to uniform standard. | |
| """ | |
| session.install(BLACK_VERSION) | |
| session.run("black", *BLACK_EXCLUDES, *BLACK_PATHS) | |
| def install_prerelease_dependencies(session, constraints_path): | |
| with open(constraints_path, encoding="utf-8") as constraints_file: | |
| constraints_text = constraints_file.read() | |
| # Ignore leading whitespace and comment lines. | |
| constraints_deps = [ | |
| match.group(1) | |
| for match in re.finditer( | |
| r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE | |
| ) | |
| ] | |
| session.install(*constraints_deps) | |
| prerel_deps = [ | |
| "google-auth", | |
| "googleapis-common-protos", | |
| # Exclude grpcio!=1.67.0rc1 which does not support python 3.13 | |
| "grpcio!=1.67.0rc1", | |
| "grpcio-status", | |
| "proto-plus", | |
| "protobuf", | |
| ] | |
| for dep in prerel_deps: | |
| session.install("--pre", "--no-deps", "--upgrade", dep) | |
| # Remaining dependencies | |
| other_deps = [ | |
| "requests", | |
| ] | |
| session.install(*other_deps) | |
| def default(session, install_grpc=True, prerelease=False, install_async_rest=False): | |
| """Default unit test session. | |
| This is intended to be run **without** an interpreter set, so | |
| that the current ``python`` (on the ``PATH``) or the version of | |
| Python corresponding to the ``nox`` binary the ``PATH`` can | |
| run the tests. | |
| """ | |
| if prerelease and not install_grpc: | |
| unittest.skip("The pre-release session cannot be run without grpc") | |
| session.install( | |
| "dataclasses", | |
| "mock; python_version=='3.7'", | |
| "pytest", | |
| "pytest-cov", | |
| "pytest-xdist", | |
| ) | |
| install_extras = [] | |
| if install_grpc: | |
| # Note: The extra is called `grpc` and not `grpcio`. | |
| install_extras.append("grpc") | |
| constraints_dir = str(CURRENT_DIRECTORY / "testing") | |
| if install_async_rest: | |
| install_extras.append("async_rest") | |
| constraints_type = "async-rest-" | |
| else: | |
| constraints_type = "" | |
| lib_with_extras = f".[{','.join(install_extras)}]" if len(install_extras) else "." | |
| if prerelease: | |
| install_prerelease_dependencies( | |
| session, | |
| f"{constraints_dir}/constraints-{constraints_type}{PYTHON_VERSIONS[0]}.txt", | |
| ) | |
| # This *must* be the last install command to get the package from source. | |
| session.install("-e", lib_with_extras, "--no-deps") | |
| else: | |
| constraints_file = ( | |
| f"{constraints_dir}/constraints-{constraints_type}{session.python}.txt" | |
| ) | |
| # fall back to standard constraints file | |
| if not pathlib.Path(constraints_file).exists(): | |
| constraints_file = f"{constraints_dir}/constraints-{session.python}.txt" | |
| session.install( | |
| "-e", | |
| lib_with_extras, | |
| "-c", | |
| constraints_file, | |
| ) | |
| # Print out package versions of dependencies | |
| session.run( | |
| "python", "-c", "import google.protobuf; print(google.protobuf.__version__)" | |
| ) | |
| # Support for proto.version was added in v1.23.0 | |
| # https://github.com/googleapis/proto-plus-python/releases/tag/v1.23.0 | |
| session.run( | |
| "python", | |
| "-c", | |
| """import proto; hasattr(proto, "version") and print(proto.version.__version__)""", | |
| ) | |
| if install_grpc: | |
| session.run("python", "-c", "import grpc; print(grpc.__version__)") | |
| session.run("python", "-c", "import google.auth; print(google.auth.__version__)") | |
| pytest_args = [ | |
| "python", | |
| "-m", | |
| "pytest", | |
| *( | |
| # Helpful for running a single test or testfile. | |
| session.posargs | |
| or [ | |
| "--quiet", | |
| "--cov=google.api_core", | |
| "--cov=tests.unit", | |
| "--cov-append", | |
| "--cov-config=.coveragerc", | |
| "--cov-report=", | |
| "--cov-fail-under=0", | |
| # Running individual tests with parallelism enabled is usually not helpful. | |
| "-n=auto", | |
| os.path.join("tests", "unit"), | |
| ] | |
| ), | |
| ] | |
| session.install("asyncmock", "pytest-asyncio") | |
| # Having positional arguments means the user wants to run specific tests. | |
| # Best not to add additional tests to that list. | |
| if not session.posargs: | |
| pytest_args.append("--cov=tests.asyncio") | |
| pytest_args.append(os.path.join("tests", "asyncio")) | |
| session.run(*pytest_args) | |
| @nox.session(python=PYTHON_VERSIONS) | |
| def unit(session): | |
| """Run the unit test suite.""" | |
| default(session) | |
| @nox.session(python=PYTHON_VERSIONS) | |
| def unit_w_prerelease_deps(session): | |
| """Run the unit test suite.""" | |
| default(session, prerelease=True) | |
| @nox.session(python=PYTHON_VERSIONS) | |
| def unit_grpc_gcp(session): | |
| """ | |
| Run the unit test suite with grpcio-gcp installed. | |
| `grpcio-gcp` doesn't support protobuf 4+. | |
| Remove extra `grpcgcp` when protobuf 3.x is dropped. | |
| https://github.com/googleapis/python-api-core/issues/594 | |
| """ | |
| constraints_path = str( | |
| CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" | |
| ) | |
| # Install grpcio-gcp | |
| session.install("-e", ".[grpcgcp]", "-c", constraints_path) | |
| # Install protobuf < 4.0.0 | |
| session.install("protobuf<4.0.0") | |
| default(session) | |
| @nox.session(python=PYTHON_VERSIONS) | |
| def unit_wo_grpc(session): | |
| """Run the unit test suite w/o grpcio installed""" | |
| default(session, install_grpc=False) | |
| @nox.session(python=PYTHON_VERSIONS) | |
| def unit_w_async_rest_extra(session): | |
| """Run the unit test suite with the `async_rest` extra""" | |
| default(session, install_async_rest=True) | |
| @nox.session(python=DEFAULT_PYTHON_VERSION) | |
| def lint_setup_py(session): | |
| """Verify that setup.py is valid (including RST check).""" | |
| session.install("docutils", "Pygments") | |
| session.run("python", "setup.py", "check", "--restructuredtext", "--strict") | |
| @nox.session(python=DEFAULT_PYTHON_VERSION) | |
| def pytype(session): | |
| """Run type-checking.""" | |
| session.install(".[grpc]", "pytype") | |
| session.run("pytype") | |
| @nox.session(python=DEFAULT_PYTHON_VERSION) | |
| def mypy(session): | |
| """Run type-checking.""" | |
| session.install(".[grpc,async_rest]", "mypy") | |
| session.install( | |
| "types-setuptools", | |
| "types-requests", | |
| "types-protobuf", | |
| "types-dataclasses", | |
| "types-mock; python_version=='3.7'", | |
| ) | |
| session.run("mypy", "google", "tests") | |
| @nox.session(python=DEFAULT_PYTHON_VERSION) | |
| def cover(session): | |
| """Run the final coverage report. | |
| This outputs the coverage report aggregating coverage from the unit | |
| test runs (not system test runs), and then erases coverage data. | |
| """ | |
| session.install("coverage", "pytest-cov") | |
| session.run("coverage", "report", "--show-missing", "--fail-under=100") | |
| session.run("coverage", "erase") | |
| @nox.session(python="3.10") | |
| def docs(session): | |
| """Build the docs for this library.""" | |
| session.install("-e", ".[grpc]") | |
| 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", | |
| "alabaster", | |
| "recommonmark", | |
| ) | |
| shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) | |
| session.run( | |
| "sphinx-build", | |
| "-W", # warnings as errors | |
| "-T", # show full traceback on exception | |
| "-N", # no colors | |
| "-b", | |
| "html", | |
| "-d", | |
| os.path.join("docs", "_build", "doctrees", ""), | |
| os.path.join("docs", ""), | |
| os.path.join("docs", "_build", "html", ""), | |
| ) | |
| @nox.session(python="3.10") | |
| def docfx(session): | |
| """Build the docfx yaml files for this library.""" | |
| session.install("-e", ".") | |
| 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", | |
| "gcp-sphinx-docfx-yaml", | |
| "alabaster", | |
| "recommonmark", | |
| ) | |
| shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) | |
| session.run( | |
| "sphinx-build", | |
| "-T", # show full traceback on exception | |
| "-N", # no colors | |
| "-D", | |
| ( | |
| "extensions=sphinx.ext.autodoc," | |
| "sphinx.ext.autosummary," | |
| "docfx_yaml.extension," | |
| "sphinx.ext.intersphinx," | |
| "sphinx.ext.coverage," | |
| "sphinx.ext.napoleon," | |
| "sphinx.ext.todo," | |
| "sphinx.ext.viewcode," | |
| "recommonmark" | |
| ), | |
| "-b", | |
| "html", | |
| "-d", | |
| os.path.join("docs", "_build", "doctrees", ""), | |
| os.path.join("docs", ""), | |
| os.path.join("docs", "_build", "html", ""), | |
| ) |