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/.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/.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..c8cd3321 100644 --- a/noxfile.py +++ b/noxfile.py @@ -28,11 +28,12 @@ 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") +EMULTATOR_INTERPRETERS = ("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", @@ -45,6 +46,24 @@ # 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", + "doctest", + "system", +] + def get_path(*names): return os.path.join(NOX_DIR, *names) @@ -78,7 +97,7 @@ def default(session): ) -@nox.session(python="3.13") +@nox.session(python=DEFAULT_INTERPRETER) @nox.parametrize( "protobuf_implementation", ["python", "upb", "cpp"], @@ -86,7 +105,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"): + 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 @@ -174,13 +198,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", @@ -252,7 +276,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 +284,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) @@ -316,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", 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/testing/constraints-3.14.txt b/testing/constraints-3.14.txt new file mode 100644 index 00000000..e69de29b 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