diff --git a/.kokoro/samples/python3.14/common.cfg b/.kokoro/samples/python3.14/common.cfg new file mode 100644 index 00000000..0fa3f1ba --- /dev/null +++ b/.kokoro/samples/python3.14/common.cfg @@ -0,0 +1,60 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.14" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-314" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-logging/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + + +############################################# +# this section merged from .kokoro/common_env_vars.cfg using owlbot.py + +env_vars: { + key: "PRODUCT_AREA_LABEL" + value: "observability" +} +env_vars: { + key: "PRODUCT_LABEL" + value: "logging" +} +env_vars: { + key: "LANGUAGE_LABEL" + value: "python" +} + +################################################### + diff --git a/.kokoro/samples/python3.14/continuous.cfg b/.kokoro/samples/python3.14/continuous.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.14/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.14/periodic-head.cfg b/.kokoro/samples/python3.14/periodic-head.cfg new file mode 100644 index 00000000..7e2973e3 --- /dev/null +++ b/.kokoro/samples/python3.14/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-logging/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.14/periodic.cfg b/.kokoro/samples/python3.14/periodic.cfg new file mode 100644 index 00000000..71cd1e59 --- /dev/null +++ b/.kokoro/samples/python3.14/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/.kokoro/samples/python3.14/presubmit.cfg b/.kokoro/samples/python3.14/presubmit.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.14/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.librarian/generator-input/librarian.py b/.librarian/generator-input/librarian.py index bf358a4b..e43640da 100644 --- a/.librarian/generator-input/librarian.py +++ b/.librarian/generator-input/librarian.py @@ -96,7 +96,8 @@ def place_before(path, text, *before_text, escape=None): "google-cloud-testutils", "opentelemetry-sdk" ], - system_test_python_versions=["3.12"], + system_test_python_versions=["3.14"], + unit_test_python_versions=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"], unit_test_external_dependencies=["flask", "webob", "django"], samples=True, ) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 7bbacd5c..9b6c26a1 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -22,7 +22,7 @@ In order to add a feature: documentation. - The feature must work fully on the following CPython versions: - 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 on both UNIX and Windows. + 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should @@ -72,7 +72,7 @@ We use `nox `__ to instrument our tests. - To run a single unit test:: - $ nox -s unit-3.13 -- -k + $ nox -s unit-3.14 -- -k .. note:: @@ -143,12 +143,12 @@ Running System Tests $ nox -s system # Run a single system test - $ nox -s system-3.12 -- -k + $ nox -s system-3.14 -- -k .. note:: - System tests are only configured to run under Python 3.12. + System tests are only configured to run under Python 3.14. For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local @@ -228,6 +228,7 @@ We support: - `Python 3.11`_ - `Python 3.12`_ - `Python 3.13`_ +- `Python 3.14`_ .. _Python 3.7: https://docs.python.org/3.7/ .. _Python 3.8: https://docs.python.org/3.8/ @@ -236,6 +237,7 @@ We support: .. _Python 3.11: https://docs.python.org/3.11/ .. _Python 3.12: https://docs.python.org/3.12/ .. _Python 3.13: https://docs.python.org/3.13/ +.. _Python 3.14: https://docs.python.org/3.14/ Supported versions can be found in our ``noxfile.py`` `config`_. diff --git a/noxfile.py b/noxfile.py index 5c352793..16dca84d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -32,7 +32,7 @@ ISORT_VERSION = "isort==5.11.0" LINT_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] -DEFAULT_PYTHON_VERSION = "3.10" +DEFAULT_PYTHON_VERSION = "3.14" UNIT_TEST_PYTHON_VERSIONS: List[str] = [ "3.9", @@ -40,6 +40,7 @@ "3.11", "3.12", "3.13", + "3.14", ] UNIT_TEST_STANDARD_DEPENDENCIES = [ "mock", @@ -58,7 +59,7 @@ UNIT_TEST_EXTRAS: List[str] = [] UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} -SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ["3.12"] +SYSTEM_TEST_PYTHON_VERSIONS: List[str] = [DEFAULT_PYTHON_VERSION] SYSTEM_TEST_STANDARD_DEPENDENCIES: List[str] = [ "mock", "pytest", @@ -79,7 +80,12 @@ CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() nox.options.sessions = [ - "unit", + "unit-3.9", + "unit-3.10", + "unit-3.11", + "unit-3.12", + "unit-3.13", + "unit-3.14", "system", "cover", "lint", @@ -143,7 +149,7 @@ def format(session): @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.install("setuptools", "docutils", "pygments") session.run("python", "setup.py", "check", "--restructuredtext", "--strict") @@ -182,8 +188,8 @@ def install_unittest_dependencies(session, *constraints): ) def unit(session, protobuf_implementation): # Install all test dependencies, then install this package in-place. - - if protobuf_implementation == "cpp" and session.python in ("3.11", "3.12", "3.13"): + py_version = tuple([int(v) for v in session.python.split(".")]) + if protobuf_implementation == "cpp" and py_version >= (3, 11): session.skip("cpp implementation is not supported in python 3.11+") constraints_path = str( @@ -383,7 +389,7 @@ def docfx(session): ) -@nox.session(python="3.13") +@nox.session(python=DEFAULT_PYTHON_VERSION) @nox.parametrize( "protobuf_implementation", ["python", "upb", "cpp"], @@ -391,7 +397,8 @@ def docfx(session): def prerelease_deps(session, protobuf_implementation): """Run all tests with prerelease versions of dependencies installed.""" - if protobuf_implementation == "cpp" and session.python in ("3.11", "3.12", "3.13"): + py_version = tuple([int(v) for v in session.python.split(".")]) + if protobuf_implementation == "cpp" and py_version >= (3, 11): session.skip("cpp implementation is not supported in python 3.11+") # Install all dependencies diff --git a/samples/snippets/export_test.py b/samples/snippets/export_test.py index 845359e0..c9eb18ef 100644 --- a/samples/snippets/export_test.py +++ b/samples/snippets/export_test.py @@ -50,8 +50,8 @@ def _create_sink_name(): @backoff.on_exception(backoff.expo, Exception, max_time=60, raise_on_giveup=False) -def _delete_object(obj): - obj.delete() +def _delete_object(obj, **kwargs): + obj.delete(**kwargs) # Runs once for entire test suite @@ -79,7 +79,7 @@ def cleanup_old_sinks(): # Bucket timestamp is int(time.time() * 1000) bucket_timestamp = int(match.group(1)) if TIMESTAMP - bucket_timestamp // 1000 > CLEANUP_THRESHOLD: - _delete_object(bucket) + _delete_object(bucket, force=True) @pytest.fixture diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index c9a3d1ec..c326375b 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -89,7 +89,7 @@ def get_pytest_env_vars() -> Dict[str, str]: # DO NOT EDIT - automatically generated. # All versions used to test samples. -ALL_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] +ALL_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] # Any default versions that should be ignored. IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] diff --git a/setup.py b/setup.py index c80db046..0bd8d6a8 100644 --- a/setup.py +++ b/setup.py @@ -85,6 +85,8 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Topic :: Internet", ], diff --git a/testing/constraints-3.14.txt b/testing/constraints-3.14.txt new file mode 100644 index 00000000..981d37ac --- /dev/null +++ b/testing/constraints-3.14.txt @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# This constraints file is required for unit tests. +# List all library dependencies and extras in this file. +google-api-core +google-auth +proto-plus +protobuf +google-cloud-core +google-cloud-appengine-logging +google-cloud-audit-log +grpc-google-iam-v1 +opentelemetry-api + +# optional dependencies +django +flask diff --git a/tests/system/test_system.py b/tests/system/test_system.py index 487ecde6..366ed343 100644 --- a/tests/system/test_system.py +++ b/tests/system/test_system.py @@ -721,8 +721,9 @@ def test_log_handler_otel_integration(self): self.assertTrue(entries[0].trace_sampled, expected_tracesampled) def test_log_handler_close(self): - from multiprocessing import Process + import multiprocessing + ctx = multiprocessing.get_context("fork") LOG_MESSAGE = "This is a test of handler.close before exiting." LOGGER_NAME = "close-test" handler_name = self._logger_name(LOGGER_NAME) @@ -746,7 +747,7 @@ def subprocess_main(): cloud_logger.warning(LOG_MESSAGE) handler.close() - proc = Process(target=subprocess_main) + proc = ctx.Process(target=subprocess_main) proc.start() proc.join() entries = _list_entries(logger) @@ -754,8 +755,9 @@ def subprocess_main(): self.assertEqual(entries[0].payload, LOG_MESSAGE) def test_log_client_flush_handlers(self): - from multiprocessing import Process + import multiprocessing + ctx = multiprocessing.get_context("fork") LOG_MESSAGE = "This is a test of client.flush_handlers before exiting." LOGGER_NAME = "close-test" handler_name = self._logger_name(LOGGER_NAME) @@ -779,7 +781,7 @@ def subprocess_main(): cloud_logger.warning(LOG_MESSAGE) Config.CLIENT.flush_handlers() - proc = Process(target=subprocess_main) + proc = ctx.Process(target=subprocess_main) proc.start() proc.join() entries = _list_entries(logger) @@ -1058,7 +1060,7 @@ def retryable(): # http and gapic results should be consistent self.assertEqual(gapic_list[0].insert_id, http_list[0].insert_id) # returned logs should be in descending order - self.assertEqual(gapic_list[0].payload, f"test {log_count-1}") + self.assertEqual(gapic_list[0].payload, f"test {log_count - 1}") RetryErrors( (ServiceUnavailable, InternalServerError, AssertionError),