From 9fa022033770660ccfac6370685a290f26b063ee Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 11 Nov 2025 21:29:39 +0000 Subject: [PATCH 1/6] feat: Add support for Python 3.14 --- .github/.OwlBot.lock.yaml | 2 +- .kokoro/noxfile.py | 2 +- .kokoro/samples/python3.14/common.cfg | 40 ++++++++++++++++++++ .kokoro/samples/python3.14/continuous.cfg | 6 +++ .kokoro/samples/python3.14/periodic-head.cfg | 11 ++++++ .kokoro/samples/python3.14/periodic.cfg | 6 +++ .kokoro/samples/python3.14/presubmit.cfg | 6 +++ CONTRIBUTING.rst | 6 ++- google/cloud/ndb/_datastore_api.py | 1 - google/cloud/ndb/_datastore_query.py | 1 - google/cloud/ndb/_gql.py | 11 +----- google/cloud/ndb/_legacy_entity_pb.py | 2 - google/cloud/ndb/_legacy_protocol_buffer.py | 1 - noxfile.py | 14 +++---- setup.py | 1 + tests/unit/test_model.py | 2 - 16 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 .kokoro/samples/python3.14/common.cfg create mode 100644 .kokoro/samples/python3.14/continuous.cfg create mode 100644 .kokoro/samples/python3.14/periodic-head.cfg create mode 100644 .kokoro/samples/python3.14/periodic.cfg create mode 100644 .kokoro/samples/python3.14/presubmit.cfg diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index ddde212a..335a2398 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:5581906b957284864632cde4e9c51d1cc66b0094990b27e689132fe5cd036046 + digest: sha256:fbbc8db67afd8b7d71bf694c5081a32da0c528eba166fbcffb3b6e56ddf907d5 diff --git a/.kokoro/noxfile.py b/.kokoro/noxfile.py index a169b5b5..69bcaf56 100644 --- a/.kokoro/noxfile.py +++ b/.kokoro/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/.kokoro/samples/python3.14/common.cfg b/.kokoro/samples/python3.14/common.cfg new file mode 100644 index 00000000..aafed0e8 --- /dev/null +++ b/.kokoro/samples/python3.14/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# 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-ndb/.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-ndb/.kokoro/trampoline_v2.sh" 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..2710a244 --- /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-ndb/.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/CONTRIBUTING.rst b/CONTRIBUTING.rst index fa1a057f..b78f2e1c 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -24,7 +24,7 @@ In order to add a feature to ``python-ndb``: documentation (in ``docs/``). - 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 @@ -151,7 +151,7 @@ Running System Tests .. note:: - System tests are only configured to run under Python 3.8. For + 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 @@ -267,6 +267,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/ @@ -275,6 +276,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/google/cloud/ndb/_datastore_api.py b/google/cloud/ndb/_datastore_api.py index 19d716a3..bca130a7 100644 --- a/google/cloud/ndb/_datastore_api.py +++ b/google/cloud/ndb/_datastore_api.py @@ -196,7 +196,6 @@ def __init__(self, options): self.todo = {} def full(self): - """Indicates whether more work can be added to this batch. Returns: diff --git a/google/cloud/ndb/_datastore_query.py b/google/cloud/ndb/_datastore_query.py index 7dd98a4c..72a9f8a3 100644 --- a/google/cloud/ndb/_datastore_query.py +++ b/google/cloud/ndb/_datastore_query.py @@ -808,7 +808,6 @@ def _compare(self, other): return NotImplemented for order in self.order_by: - if order.name == "__key__": this_value = helpers.key_from_protobuf( self.result_pb.entity.key diff --git a/google/cloud/ndb/_gql.py b/google/cloud/ndb/_gql.py index 0b605374..50e2d65d 100644 --- a/google/cloud/ndb/_gql.py +++ b/google/cloud/ndb/_gql.py @@ -115,7 +115,6 @@ def __init__(self, query_string, _app=None, _auth_domain=None, namespace=None): raise error def _InitializeParseState(self): - self._kind = None self._keys_only = False self._projection = None @@ -330,9 +329,7 @@ def _FilterList(self): self._CheckFilterSyntax(identifier, condition) if not self._AddSimpleFilter(identifier, condition, self._Reference()): - if not self._AddSimpleFilter(identifier, condition, self._Literal()): - type_cast = self._TypeCast() if not type_cast or not self._AddProcessedParameterFilter( identifier, condition, *type_cast @@ -378,7 +375,6 @@ def _CheckFilterSyntax(self, identifier, raw_condition): condition = raw_condition.lower() if identifier.lower() == "ancestor": if condition == "is": - if self._has_ancestor: self._Error('Only one ANCESTOR IS" clause allowed') else: @@ -508,13 +504,11 @@ def _Literal(self): self._next_symbol += 1 if literal is None: - literal = self._AcceptRegex(self._quoted_string_regex) if literal: literal = literal[1:-1].replace("''", "'") if literal is None: - if self._Accept("TRUE"): literal = True elif self._Accept("FALSE"): @@ -548,7 +542,6 @@ def _TypeCast(self, can_cast_list=True): cast_op = self._AcceptRegex(self._cast_regex) if not cast_op: if can_cast_list and self._Accept("("): - cast_op = "list" else: return None @@ -588,11 +581,9 @@ def _OrderList(self): def _Limit(self): """Consume the LIMIT clause.""" if self._Accept("LIMIT"): - maybe_limit = self._AcceptRegex(self._number_regex) if maybe_limit: - if self._Accept(","): self._offset = int(maybe_limit) maybe_limit = self._AcceptRegex(self._number_regex) @@ -674,7 +665,7 @@ def query_filters(self, model_class, filters): name, op = name_op values = gql_filters[name_op] op = op.lower() - for (func, args) in values: + for func, args in values: prop = model_class._properties.get(name) val = self._args_to_val(func, args) if isinstance(val, query_module.ParameterizedThing): diff --git a/google/cloud/ndb/_legacy_entity_pb.py b/google/cloud/ndb/_legacy_entity_pb.py index 9e651b15..d171d273 100644 --- a/google/cloud/ndb/_legacy_entity_pb.py +++ b/google/cloud/ndb/_legacy_entity_pb.py @@ -343,7 +343,6 @@ def TryMerge(self, d): class Property(ProtocolBuffer.ProtocolMessage): - NO_MEANING = 0 BLOB = 14 TEXT = 15 @@ -691,7 +690,6 @@ def TryMerge(self, d): class EntityProto(ProtocolBuffer.ProtocolMessage): - has_key_ = 0 has_owner_ = 0 owner_ = None diff --git a/google/cloud/ndb/_legacy_protocol_buffer.py b/google/cloud/ndb/_legacy_protocol_buffer.py index 2ac2ef70..0b10f0b4 100644 --- a/google/cloud/ndb/_legacy_protocol_buffer.py +++ b/google/cloud/ndb/_legacy_protocol_buffer.py @@ -137,7 +137,6 @@ def get64(self): ) def getVarInt32(self): - b = self.get8() if not (b & 128): return b diff --git a/noxfile.py b/noxfile.py index dac34d99..43a06546 100644 --- a/noxfile.py +++ b/noxfile.py @@ -28,11 +28,11 @@ LOCAL_DEPS = ("google-api-core", "google-cloud-core") NOX_DIR = os.path.abspath(os.path.dirname(__file__)) -DEFAULT_INTERPRETER = "3.8" -ALL_INTERPRETERS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13") +DEFAULT_INTERPRETER = "3.14" +ALL_INTERPRETERS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14") CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() -BLACK_VERSION = "black==22.3.0" +BLACK_VERSION = "black[jupyter]==23.7.0" UNIT_TEST_STANDARD_DEPENDENCIES = [ "mock", "asyncmock", @@ -78,7 +78,7 @@ def default(session): ) -@nox.session(python="3.13") +@nox.session(python=DEFAULT_INTERPRETER) @nox.parametrize( "protobuf_implementation", ["python", "upb", "cpp"], @@ -86,7 +86,7 @@ def default(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"): + if protobuf_implementation == "cpp" and session.python in ("3.11", "3.12", "3.13", "3.14"): session.skip("cpp implementation is not supported in python 3.11+") # Install all dependencies @@ -252,7 +252,7 @@ def lint(session): Returns a failure if the linters find linting errors or sufficiently serious code quality issues. """ - session.install("flake8", BLACK_VERSION, "click<8.1.0") + session.install("flake8", BLACK_VERSION) run_black(session, use_check=True) session.run("flake8", "google", "tests") @@ -260,7 +260,7 @@ def lint(session): @nox.session(py=DEFAULT_INTERPRETER) def blacken(session): # Install all dependencies. - session.install(BLACK_VERSION, "click<8.1.0") + session.install(BLACK_VERSION) # Run ``black``. run_black(session) diff --git a/setup.py b/setup.py index 0c0fb96f..a2e5a857 100644 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ def main(): "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/tests/unit/test_model.py b/tests/unit/test_model.py index b642aa3b..14ae8efb 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -200,7 +200,6 @@ def test___hash__(): class TestIndexState: - INDEX = mock.sentinel.index def test_constructor(self): @@ -6565,7 +6564,6 @@ def test_get_indexes(): @pytest.mark.usefixtures("in_context") def test_serialization(): - # This is needed because pickle can't serialize local objects global SomeKind, OtherKind From 3b20ae5f8a491c795e101fb64ac15714b884b7cf Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Tue, 11 Nov 2025 21:32:33 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- noxfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 43a06546..dffa5aed 100644 --- a/noxfile.py +++ b/noxfile.py @@ -86,7 +86,12 @@ def default(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", "3.14"): + if protobuf_implementation == "cpp" and session.python in ( + "3.11", + "3.12", + "3.13", + "3.14", + ): session.skip("cpp implementation is not supported in python 3.11+") # Install all dependencies From 26d5c2b5c7786143001f31e7fb1c078e18504ca3 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 11 Nov 2025 21:53:14 +0000 Subject: [PATCH 3/6] Add GH action for python 3.7/3.8 testing --- .github/workflows/unittest.yml | 61 ++++++++++++++++++++++++++++++++++ noxfile.py | 21 ++++++++++-- 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/unittest.yml diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 00000000..cc6fe2b2 --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,61 @@ +on: + pull_request: + branches: + - main +name: unittest +jobs: + unit: + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2303): use `ubuntu-latest` once this bug is fixed. + # Use ubuntu-22.04 until Python 3.7 is removed from the test matrix + # https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + runs-on: ubuntu-22.04 + strategy: + matrix: + python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run unit tests + env: + COVERAGE_FILE: .coverage-${{ matrix.python }} + run: | + nox -s unit-${{ matrix.python }} + - name: Upload coverage results + uses: actions/upload-artifact@v4 + with: + name: coverage-artifact-${{ matrix.python }} + path: .coverage-${{ matrix.python }} + include-hidden-files: true + + 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.14" + - 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 diff --git a/noxfile.py b/noxfile.py index dffa5aed..9cebf3a3 100644 --- a/noxfile.py +++ b/noxfile.py @@ -30,6 +30,7 @@ NOX_DIR = os.path.abspath(os.path.dirname(__file__)) DEFAULT_INTERPRETER = "3.14" ALL_INTERPRETERS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14") +EMULTATOR_INTERPRETERS = ("3.9", "3.10", "3.11", "3.12", "3.13", "3.14") CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() BLACK_VERSION = "black[jupyter]==23.7.0" @@ -45,6 +46,22 @@ # Error if a python version is missing nox.options.error_on_missing_interpreters = True +nox.options.sessions = [ + "prerelease_deps", + "unit-3.9", + "unit-3.10", + "unit-3.11", + "unit-3.12", + "unit-3.13", + "unit-3.14", + "cover", + "old-emulator-system" "emulator-system" "lint", + "blacken", + "docs", + "doctests", + "system", +] + def get_path(*names): return os.path.join(NOX_DIR, *names) @@ -179,13 +196,13 @@ def cover(session): session.run("coverage", "erase") -@nox.session(name="old-emulator-system", python=ALL_INTERPRETERS) +@nox.session(name="old-emulator-system", python=EMULTATOR_INTERPRETERS) def old_emulator_system(session): emulator_args = ["gcloud", "beta", "emulators", "datastore", "start"] _run_emulator(session, emulator_args) -@nox.session(name="emulator-system", python=ALL_INTERPRETERS) +@nox.session(name="emulator-system", python=EMULTATOR_INTERPRETERS) def emulator_system(session): emulator_args = [ "gcloud", From 5d00cd0c8c8c6a2bbd588a0327b73cf48d1e411f Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 12 Nov 2025 16:04:56 +0000 Subject: [PATCH 4/6] add constraints file --- testing/constraints-3.14.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 testing/constraints-3.14.txt diff --git a/testing/constraints-3.14.txt b/testing/constraints-3.14.txt new file mode 100644 index 00000000..e69de29b From cb65e7411f95615970d662b99022248e24695e42 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 12 Nov 2025 16:08:03 +0000 Subject: [PATCH 5/6] fix formatting --- noxfile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 9cebf3a3..fb3b642b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -55,7 +55,9 @@ "unit-3.13", "unit-3.14", "cover", - "old-emulator-system" "emulator-system" "lint", + "old-emulator-system", + "emulator-system", + "lint", "blacken", "docs", "doctests", From c112140b47209d32ab2e0effcfd159f888e73789 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 12 Nov 2025 16:13:37 +0000 Subject: [PATCH 6/6] fix typo --- noxfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index fb3b642b..c8cd3321 100644 --- a/noxfile.py +++ b/noxfile.py @@ -60,7 +60,7 @@ "lint", "blacken", "docs", - "doctests", + "doctest", "system", ] @@ -340,7 +340,7 @@ def doctest(session): "sphinx==4.0.1", ) session.install(".") - # Run the script for building docs and running doctests. + # Run the script for building docs and running doctest. run_args = [ "sphinx-build", "-W",