diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml
index 508ba98efe..9a7846675f 100644
--- a/.github/.OwlBot.lock.yaml
+++ b/.github/.OwlBot.lock.yaml
@@ -13,5 +13,5 @@
# limitations under the License.
docker:
image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest
- digest: sha256:25de45b58e52021d3a24a6273964371a97a4efeefe6ad3845a64e697c63b6447
-# created: 2025-04-14T14:34:43.260858345Z
+ digest: sha256:4a9e5d44b98e8672e2037ee22bc6b4f8e844a2d75fcb78ea8a4b38510112abc6
+# created: 2025-10-07
diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml
index 9920db74d5..389c3747c3 100644
--- a/.github/sync-repo-settings.yaml
+++ b/.github/sync-repo-settings.yaml
@@ -17,18 +17,18 @@ branchProtectionRules:
# List of required status check contexts that must pass for commits to be accepted to matching branches.
requiredStatusCheckContexts:
- 'Kokoro'
- - 'Kokoro system-3.7'
+ - 'Kokoro system'
- 'cla/google'
- 'OwlBot Post Processor'
- 'docs'
- 'docfx'
- 'lint'
- - 'unit (3.7)'
- - 'unit (3.8)'
- 'unit (3.9)'
- 'unit (3.10)'
- 'unit (3.11)'
- 'unit (3.12)'
+ - 'unit (3.13)'
+ - 'unit (3.14)'
- 'cover'
- 'run-systests'
# List of explicit permissions to add (additive only)
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 4866193af2..9a0598202b 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -12,7 +12,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
- python-version: "3.8"
+ python-version: "3.13"
- name: Install nox
run: |
python -m pip install --upgrade setuptools pip wheel
diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml
index 772186478f..27075146a1 100644
--- a/.github/workflows/mypy.yml
+++ b/.github/workflows/mypy.yml
@@ -12,7 +12,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
- python-version: "3.8"
+ python-version: "3.13"
- name: Install nox
run: |
python -m pip install --upgrade setuptools pip wheel
diff --git a/.github/workflows/system_emulated.yml b/.github/workflows/system_emulated.yml
index 0f3a69224b..bb7986a0ab 100644
--- a/.github/workflows/system_emulated.yml
+++ b/.github/workflows/system_emulated.yml
@@ -17,7 +17,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
- python-version: '3.7'
+ python-version: '3.13'
# firestore emulator requires java 21+
- name: Setup Java
diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml
index c66b757ced..494bb568fe 100644
--- a/.github/workflows/unittest.yml
+++ b/.github/workflows/unittest.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
- python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
+ python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -45,7 +45,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
- python-version: "3.8"
+ python-version: "3.13"
- name: Install coverage
run: |
python -m pip install --upgrade setuptools pip wheel
diff --git a/.kokoro/presubmit/system-3.14.cfg b/.kokoro/presubmit/system-3.14.cfg
new file mode 100644
index 0000000000..86e7c5d776
--- /dev/null
+++ b/.kokoro/presubmit/system-3.14.cfg
@@ -0,0 +1,7 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Only run this nox session.
+env_vars: {
+ key: "NOX_SESSION"
+ value: "system-3.14"
+}
\ No newline at end of file
diff --git a/.kokoro/presubmit/system-3.9.cfg b/.kokoro/presubmit/system-3.9.cfg
new file mode 100644
index 0000000000..b8ae66b376
--- /dev/null
+++ b/.kokoro/presubmit/system-3.9.cfg
@@ -0,0 +1,7 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Only run this nox session.
+env_vars: {
+ key: "NOX_SESSION"
+ value: "system-3.9"
+}
\ No newline at end of file
diff --git a/.kokoro/presubmit/system.cfg b/.kokoro/presubmit/system.cfg
new file mode 100644
index 0000000000..bd1fb514b2
--- /dev/null
+++ b/.kokoro/presubmit/system.cfg
@@ -0,0 +1,7 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Only run this nox session.
+env_vars: {
+ key: "NOX_SESSION"
+ value: "system-3.9"
+}
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 1d0c00be3e..c917685242 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::
@@ -238,6 +238,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/
@@ -245,7 +246,7 @@ We support:
.. _Python 3.10: https://docs.python.org/3.10/
.. _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/firestore_bundle/bundle.py b/google/cloud/firestore_bundle/bundle.py
index 0f9aaed976..e985a1e065 100644
--- a/google/cloud/firestore_bundle/bundle.py
+++ b/google/cloud/firestore_bundle/bundle.py
@@ -344,9 +344,10 @@ def build(self) -> str:
BundleElement(document_metadata=bundled_document.metadata)
)
document_count += 1
+ bundle_pb = bundled_document.snapshot._to_protobuf()
buffer += self._compile_bundle_element(
BundleElement(
- document=bundled_document.snapshot._to_protobuf()._pb,
+ document=bundle_pb._pb if bundle_pb else None,
)
)
diff --git a/google/cloud/firestore_v1/async_transaction.py b/google/cloud/firestore_v1/async_transaction.py
index 36509941ed..0dfa82e011 100644
--- a/google/cloud/firestore_v1/async_transaction.py
+++ b/google/cloud/firestore_v1/async_transaction.py
@@ -21,8 +21,10 @@
AsyncGenerator,
Awaitable,
Callable,
+ Generic,
Optional,
)
+from typing_extensions import Concatenate, ParamSpec, TypeVar
from google.api_core import exceptions, gapic_v1
from google.api_core import retry_async as retries
@@ -44,14 +46,14 @@
# Types needed only for Type Hints
if TYPE_CHECKING: # pragma: NO COVER
import datetime
- from typing_extensions import TypeVar, ParamSpec, Concatenate
from google.cloud.firestore_v1.async_stream_generator import AsyncStreamGenerator
from google.cloud.firestore_v1.base_document import DocumentSnapshot
from google.cloud.firestore_v1.query_profile import ExplainOptions
- T = TypeVar("T")
- P = ParamSpec("P")
+
+T = TypeVar("T")
+P = ParamSpec("P")
class AsyncTransaction(async_batch.AsyncWriteBatch, BaseTransaction):
@@ -253,7 +255,7 @@ async def get(
)
-class _AsyncTransactional(_BaseTransactional):
+class _AsyncTransactional(_BaseTransactional, Generic[T, P]):
"""Provide a callable object to use as a transactional decorater.
This is surfaced via
diff --git a/google/cloud/firestore_v1/base_query.py b/google/cloud/firestore_v1/base_query.py
index 14df886bcb..2de95b79ad 100644
--- a/google/cloud/firestore_v1/base_query.py
+++ b/google/cloud/firestore_v1/base_query.py
@@ -929,7 +929,7 @@ def _normalize_cursor(self, cursor, orders) -> Tuple[List, bool] | None:
if isinstance(document_fields, document.DocumentSnapshot):
snapshot = document_fields
- document_fields = snapshot.to_dict()
+ document_fields = copy.deepcopy(snapshot._data)
document_fields["__name__"] = snapshot.reference
if isinstance(document_fields, dict):
diff --git a/mypy.ini b/mypy.ini
index beaa679a8d..59a6e4d37a 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,3 +1,7 @@
[mypy]
-python_version = 3.8
+python_version = 3.13
namespace_packages = True
+
+# ignore gapic files
+[mypy-google.cloud.firestore_v1.services.*]
+ignore_errors = True
\ No newline at end of file
diff --git a/noxfile.py b/noxfile.py
index 9e81d71795..ac1c7ee4d1 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -33,7 +33,7 @@
ISORT_VERSION = "isort==5.11.0"
LINT_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"]
-DEFAULT_PYTHON_VERSION = "3.8"
+DEFAULT_PYTHON_VERSION = "3.13"
UNIT_TEST_PYTHON_VERSIONS: List[str] = [
"3.7",
@@ -43,6 +43,7 @@
"3.11",
"3.12",
"3.13",
+ "3.14",
]
UNIT_TEST_STANDARD_DEPENDENCIES = [
"mock",
@@ -61,7 +62,7 @@
UNIT_TEST_EXTRAS: List[str] = []
UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {}
-SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ["3.7"]
+SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ["3.9", "3.14"]
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_emulated",
"system",
"mypy",
@@ -170,7 +176,7 @@ def mypy(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")
@@ -210,7 +216,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(
@@ -375,7 +382,13 @@ def cover(session):
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",
+ "report",
+ "--show-missing",
+ "--fail-under=100",
+ "--omit=tests/*",
+ )
session.run("coverage", "erase")
@@ -469,7 +482,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/owlbot.py b/owlbot.py
index f08048fef7..a9323ce3c0 100644
--- a/owlbot.py
+++ b/owlbot.py
@@ -138,12 +138,14 @@ def update_fixup_scripts(library):
# ----------------------------------------------------------------------------
templated_files = common.py_library(
samples=False, # set to True only if there are samples
- system_test_python_versions=["3.7"],
unit_test_external_dependencies=["aiounittest", "six", "freezegun"],
system_test_external_dependencies=["pytest-asyncio", "six"],
microgenerator=True,
cov_level=100,
split_system_tests=True,
+ default_python_version="3.13",
+ system_test_python_versions=["3.9", "3.14"],
+ unit_test_python_versions=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"],
)
s.move(templated_files,
diff --git a/pytest.ini b/pytest.ini
index 099cbd3ad2..eac8ea1233 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -18,3 +18,8 @@ filterwarnings =
ignore:After January 1, 2024, new releases of this library will drop support for Python 3.7:DeprecationWarning
# Remove warning once https://github.com/googleapis/gapic-generator-python/issues/1939 is fixed
ignore:get_mtls_endpoint_and_cert_source is deprecated.:DeprecationWarning
+ # Remove once credential file support is removed
+ ignore:.*The \`credentials_file\` argument is deprecated.*:DeprecationWarning
+ # Remove after updating test dependencies that use asyncio.iscoroutinefunction
+ ignore:.*\'asyncio.iscoroutinefunction\' is deprecated.*:DeprecationWarning
+ ignore:.*\'asyncio.get_event_loop_policy\' is deprecated.*:DeprecationWarning
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 2a47080a15..8625abce96 100644
--- a/setup.py
+++ b/setup.py
@@ -79,6 +79,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",
"Topic :: Software Development :: Libraries :: Python Modules",
diff --git a/testing/constraints-3.14.txt b/testing/constraints-3.14.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/unit/v1/test_base_query.py b/tests/unit/v1/test_base_query.py
index 7f6b0e5e2e..7804b0430f 100644
--- a/tests/unit/v1/test_base_query.py
+++ b/tests/unit/v1/test_base_query.py
@@ -1400,6 +1400,19 @@ def test_basequery__normalize_cursor_as_snapshot_hit():
assert query._normalize_cursor(cursor, query._orders) == ([1], True)
+def test_basequery__normalize_cursor_non_existant_snapshot():
+ from google.cloud.firestore_v1 import document
+
+ values = {"b": 1}
+ docref = _make_docref("here", "doc_id")
+ snapshot = document.DocumentSnapshot(docref, values, False, None, None, None)
+ cursor = (snapshot, True)
+ collection = _make_collection("here")
+ query = _make_base_query(collection).order_by("b", "ASCENDING")
+
+ assert query._normalize_cursor(cursor, query._orders) == ([1], True)
+
+
def test_basequery__normalize_cursor_w___name___w_reference():
db_string = "projects/my-project/database/(default)"
client = mock.Mock(spec=["_database_string"])