diff --git a/.cross_sync/transformers.py b/.cross_sync/transformers.py
index 42ba3f83c..9adadd0aa 100644
--- a/.cross_sync/transformers.py
+++ b/.cross_sync/transformers.py
@@ -71,18 +71,19 @@ def visit_FunctionDef(self, node):
Replace function docstrings
"""
docstring = ast.get_docstring(node)
- if docstring and isinstance(node.body[0], ast.Expr) and isinstance(
- node.body[0].value, ast.Str
- ):
+ if docstring and isinstance(node.body[0], ast.Expr) \
+ and isinstance(node.body[0].value, ast.Constant) \
+ and isinstance(node.body[0].value.value, str) \
+ :
for key_word, replacement in self.replacements.items():
docstring = docstring.replace(key_word, replacement)
- node.body[0].value.s = docstring
+ node.body[0].value.value = docstring
return self.generic_visit(node)
def visit_Constant(self, node):
"""Replace string type annotations"""
try:
- node.s = self.replacements.get(node.s, node.s)
+ node.value = self.replacements.get(node.value, node.value)
except TypeError:
# ignore unhashable types (e.g. list)
pass
@@ -264,7 +265,7 @@ def get_output_path(self, node):
for target in n.targets:
if isinstance(target, ast.Name) and target.id == self.FILE_ANNOTATION:
# return the output path
- return n.value.s.replace(".", "/") + ".py"
+ return n.value.value.replace(".", "/") + ".py"
def visit_Module(self, node):
# look for __CROSS_SYNC_OUTPUT__ Assign statement
diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml
index c631e1f7d..9a7846675 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:5581906b957284864632cde4e9c51d1cc66b0094990b27e689132fe5cd036046
-# created: 2025-03-05
+ digest: sha256:4a9e5d44b98e8672e2037ee22bc6b4f8e844a2d75fcb78ea8a4b38510112abc6
+# created: 2025-10-07
diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml
index df49eafcc..14e32d6fc 100644
--- a/.github/sync-repo-settings.yaml
+++ b/.github/sync-repo-settings.yaml
@@ -29,9 +29,19 @@ branchProtectionRules:
# List of required status check contexts that must pass for commits to be accepted to matching branches.
requiredStatusCheckContexts:
- 'Kokoro'
- - 'Kokoro system-3.8'
+ - 'Kokoro system'
- 'cla/google'
- 'OwlBot Post Processor'
+ - 'lint'
+ - 'mypy'
+ - 'docs'
+ - 'docfx'
+ - 'unit-3.9'
+ - 'unit-3.10'
+ - 'unit-3.11'
+ - 'unit-3.12'
+ - 'unit-3.13'
+ - 'unit-3.14'
# List of explicit permissions to add (additive only)
permissionRules:
# Team slug to add to repository permissions
diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml
index 6a96a87d3..f7396eaa9 100644
--- a/.github/workflows/conformance.yaml
+++ b/.github/workflows/conformance.yaml
@@ -25,7 +25,7 @@ jobs:
strategy:
matrix:
test-version: [ "v0.0.4" ]
- py-version: [ 3.8 ]
+ py-version: [ 3.13 ]
client-type: [ "async", "sync"]
# None of the clients currently support reverse scans, execute query plan refresh, retry info, or routing cookie
include:
diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml
index 3915cddd3..f2b78a536 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 c9dab998c..d8bbbb639 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.8'
+ python-version: '3.13'
- name: Setup GCloud SDK
uses: google-github-actions/setup-gcloud@v2.1.1
diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml
index 6a0429d96..d59bbb1b8 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
diff --git a/.kokoro/presubmit/system-3.8.cfg b/.kokoro/presubmit/system-3.9.cfg
similarity index 83%
rename from .kokoro/presubmit/system-3.8.cfg
rename to .kokoro/presubmit/system-3.9.cfg
index f4bcee3db..b8ae66b37 100644
--- a/.kokoro/presubmit/system-3.8.cfg
+++ b/.kokoro/presubmit/system-3.9.cfg
@@ -3,5 +3,5 @@
# Only run this nox session.
env_vars: {
key: "NOX_SESSION"
- value: "system-3.8"
+ 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 000000000..b8ae66b37
--- /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"
+}
\ No newline at end of file
diff --git a/.kokoro/samples/python3.14/common.cfg b/.kokoro/samples/python3.14/common.cfg
new file mode 100644
index 000000000..a9ea06119
--- /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-bigtable/.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-bigtable/.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 000000000..a1c8d9759
--- /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 000000000..be25a34f9
--- /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-bigtable/.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 000000000..71cd1e597
--- /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 000000000..a1c8d9759
--- /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 985538f48..07ac8f218 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.8 -- -k
+ $ nox -s system-3.9 -- -k
.. note::
- System tests are only configured to run under Python 3.8.
+ System tests are only configured to run under Python 3.9.
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/google/cloud/bigtable/data/exceptions.py b/google/cloud/bigtable/data/exceptions.py
index 5645ae3aa..b19e0e5ea 100644
--- a/google/cloud/bigtable/data/exceptions.py
+++ b/google/cloud/bigtable/data/exceptions.py
@@ -90,7 +90,7 @@ def __init__(self, message, excs):
# apply index header
if idx != 0:
message_parts.append(
- f"+---------------- {str(idx+1).rjust(2)} ----------------"
+ f"+---------------- {str(idx + 1).rjust(2)} ----------------"
)
cause = e.__cause__
# if this exception was had a cause, print the cause first
diff --git a/google/cloud/bigtable/data/row.py b/google/cloud/bigtable/data/row.py
index a5575b83a..50e65a958 100644
--- a/google/cloud/bigtable/data/row.py
+++ b/google/cloud/bigtable/data/row.py
@@ -190,7 +190,7 @@ def __str__(self) -> str:
elif len(cell_list) == 1:
line.append(f"[{cell_list[0]}],")
else:
- line.append(f"[{cell_list[0]}, (+{len(cell_list)-1} more)],")
+ line.append(f"[{cell_list[0]}, (+{len(cell_list) - 1} more)],")
output.append("".join(line))
output.append("}")
return "\n".join(output)
diff --git a/mypy.ini b/mypy.ini
index 31cc24223..701b7587c 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,6 +1,9 @@
[mypy]
-python_version = 3.8
+python_version = 3.13
namespace_packages = True
+check_untyped_defs = True
+warn_unreachable = True
+disallow_any_generics = True
exclude = tests/unit/gapic/
[mypy-grpc.*]
@@ -26,3 +29,10 @@ ignore_missing_imports = True
[mypy-pytest]
ignore_missing_imports = True
+
+[mypy-google.cloud.*]
+ignore_errors = True
+
+# only verify data client
+[mypy-google.cloud.bigtable.data.*]
+ignore_errors = False
diff --git a/noxfile.py b/noxfile.py
index 548bfd0ec..a182bafba 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.8"
+DEFAULT_PYTHON_VERSION = "3.13"
UNIT_TEST_PYTHON_VERSIONS: List[str] = [
"3.7",
@@ -42,6 +42,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.8", "3.12"]
+SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ["3.9", "3.14"]
SYSTEM_TEST_STANDARD_DEPENDENCIES: List[str] = [
"mock",
"pytest",
@@ -78,7 +79,12 @@
# 'docfx' is excluded since it only needs to run in 'docs-presubmit'
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",
@@ -148,26 +154,13 @@ def mypy(session):
"mypy", "types-setuptools", "types-protobuf", "types-mock", "types-requests"
)
session.install("google-cloud-testutils")
- session.run(
- "mypy",
- "-p",
- "google.cloud.bigtable.data",
- "--check-untyped-defs",
- "--warn-unreachable",
- "--disallow-any-generics",
- "--exclude",
- "tests/system/v2_client",
- "--exclude",
- "tests/unit/v2_client",
- "--disable-error-code",
- "func-returns-value", # needed for CrossSync.rm_aio
- )
+ session.run("mypy", "-p", "google.cloud.bigtable.data")
@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")
@@ -206,8 +199,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(
@@ -270,7 +263,7 @@ def install_systemtest_dependencies(session, *constraints):
session.install("-e", ".", *constraints)
-@nox.session(python="3.8")
+@nox.session(python=DEFAULT_PYTHON_VERSION)
def system_emulated(session):
import subprocess
import signal
@@ -456,7 +449,7 @@ def docfx(session):
session.run("python", "docs/scripts/patch_devsite_toc.py")
-@nox.session(python="3.12")
+@nox.session(python="3.14")
@nox.parametrize(
"protobuf_implementation",
["python", "upb", "cpp"],
@@ -464,7 +457,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 9562b6142..b6b741b54 100644
--- a/owlbot.py
+++ b/owlbot.py
@@ -109,6 +109,8 @@ def get_staging_dirs(
system_test_external_dependencies=[
"pytest-asyncio==0.21.2",
],
+ system_test_python_versions=["3.9"],
+ unit_test_python_versions=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"],
)
s.move(templated_files, excludes=[".coveragerc", "README.rst", ".github/release-please.yml", "noxfile.py", "renovate.json"])
diff --git a/setup.py b/setup.py
index 3cb9d465d..cac533db6 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 000000000..e69de29bb
diff --git a/tests/system/conftest.py b/tests/system/conftest.py
index 39480942d..8c0eb30b1 100644
--- a/tests/system/conftest.py
+++ b/tests/system/conftest.py
@@ -30,7 +30,7 @@
@pytest.fixture(scope="session")
def event_loop():
- loop = asyncio.get_event_loop()
+ loop = asyncio.new_event_loop()
yield loop
loop.stop()
loop.close()
diff --git a/tests/unit/admin_overlay/test_async_consistency.py b/tests/unit/admin_overlay/test_async_consistency.py
index 56978713c..b64ae1a11 100644
--- a/tests/unit/admin_overlay/test_async_consistency.py
+++ b/tests/unit/admin_overlay/test_async_consistency.py
@@ -43,7 +43,8 @@ def async_mock_check_consistency_callable(max_poll_count=1):
return mock.AsyncMock(spec=["__call__"], side_effect=side_effect)
-def test_check_consistency_future_cancel():
+@pytest.mark.asyncio
+async def test_check_consistency_future_cancel():
check_consistency_call = async_mock_check_consistency_callable()
future = async_consistency._AsyncCheckConsistencyPollingFuture(
check_consistency_call