diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml
index 2d1193d6..a82859e1 100644
--- a/.github/workflows/unittest.yml
+++ b/.github/workflows/unittest.yml
@@ -19,6 +19,7 @@ jobs:
- "3.10"
- "3.11"
- "3.12"
+ - "3.13"
exclude:
- option: "_wo_grpc"
python: 3.7
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 8d1475ce..1a1f608b 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -21,7 +21,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 and 3.12 on both UNIX and Windows.
+ 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 on both UNIX and Windows.
- The feature must not add unnecessary dependencies (where
"unnecessary" is of course subjective, but new dependencies should
@@ -71,7 +71,7 @@ We use `nox `__ to instrument our tests.
- To run a single unit test::
- $ nox -s unit-3.12 -- -k
+ $ nox -s unit-3.13 -- -k
.. note::
@@ -203,6 +203,7 @@ We support:
- `Python 3.10`_
- `Python 3.11`_
- `Python 3.12`_
+- `Python 3.13`_
.. _Python 3.7: https://docs.python.org/3.7/
.. _Python 3.8: https://docs.python.org/3.8/
@@ -210,6 +211,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/
Supported versions can be found in our ``noxfile.py`` `config`_.
diff --git a/noxfile.py b/noxfile.py
index 3dbd27df..ada6f330 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -28,7 +28,7 @@
# Black and flake8 clash on the syntax for ignoring flake8's F401 in this file.
BLACK_EXCLUDES = ["--exclude", "^/google/api_core/operations_v1/__init__.py"]
-PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
+PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
DEFAULT_PYTHON_VERSION = "3.10"
CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
@@ -95,7 +95,8 @@ def install_prerelease_dependencies(session, constraints_path):
prerel_deps = [
"google-auth",
"googleapis-common-protos",
- "grpcio",
+ # Exclude grpcio!=1.67.0rc1 which does not support python 3.13
+ "grpcio!=1.67.0rc1",
"grpcio-status",
"proto-plus",
"protobuf",
@@ -132,6 +133,7 @@ def default(session, install_grpc=True, prerelease=False, install_async_rest=Fal
install_extras = []
if install_grpc:
+ # Note: The extra is called `grpc` and not `grpcio`.
install_extras.append("grpc")
constraints_dir = str(CURRENT_DIRECTORY / "testing")
diff --git a/setup.py b/setup.py
index d3c2a2b4..a15df560 100644
--- a/setup.py
+++ b/setup.py
@@ -93,6 +93,7 @@
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Operating System :: OS Independent",
"Topic :: Internet",
],
diff --git a/testing/constraints-3.13.txt b/testing/constraints-3.13.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/asyncio/retry/test_retry_streaming_async.py b/tests/asyncio/retry/test_retry_streaming_async.py
index ffdf314a..d0fd799c 100644
--- a/tests/asyncio/retry/test_retry_streaming_async.py
+++ b/tests/asyncio/retry/test_retry_streaming_async.py
@@ -139,6 +139,7 @@ async def test___call___generator_retry(self, sleep):
unpacked = [await generator.__anext__() for i in range(10)]
assert unpacked == [0, 1, 2, 0, 1, 2, 0, 1, 2, 0]
assert on_error.call_count == 3
+ await generator.aclose()
@mock.patch("random.uniform", autospec=True, side_effect=lambda m, n: n)
@mock.patch("asyncio.sleep", autospec=True)
@@ -246,6 +247,7 @@ async def _mock_send_gen():
recv = await generator.asend(msg)
out_messages.append(recv)
assert in_messages == out_messages
+ await generator.aclose()
@mock.patch("asyncio.sleep", autospec=True)
@pytest.mark.asyncio
@@ -263,6 +265,7 @@ async def test___call___generator_send_retry(self, sleep):
with pytest.raises(TypeError) as exc_info:
await generator.asend("cannot send to fresh generator")
assert exc_info.match("can't send non-None value")
+ await generator.aclose()
# error thrown on 3
# generator should contain 0, 1, 2 looping
@@ -271,6 +274,7 @@ async def test___call___generator_send_retry(self, sleep):
unpacked = [await generator.asend(i) for i in range(10)]
assert unpacked == [1, 2, 0, 1, 2, 0, 1, 2, 0, 1]
assert on_error.call_count == 3
+ await generator.aclose()
@mock.patch("asyncio.sleep", autospec=True)
@pytest.mark.asyncio
@@ -382,6 +386,7 @@ async def wrapper():
assert await retryable.asend("test") == 1
assert await retryable.asend("test2") == 2
assert await retryable.asend("test3") == 3
+ await retryable.aclose()
@pytest.mark.parametrize("awaitable_wrapped", [True, False])
@mock.patch("asyncio.sleep", autospec=True)
diff --git a/tests/asyncio/test_page_iterator_async.py b/tests/asyncio/test_page_iterator_async.py
index d8bba934..63e26d02 100644
--- a/tests/asyncio/test_page_iterator_async.py
+++ b/tests/asyncio/test_page_iterator_async.py
@@ -110,6 +110,7 @@ async def test__page_aiter_increment(self):
await page_aiter.__anext__()
assert iterator.num_results == 1
+ await page_aiter.aclose()
@pytest.mark.asyncio
async def test__page_aiter_no_increment(self):
@@ -122,6 +123,7 @@ async def test__page_aiter_no_increment(self):
# results should still be 0 after fetching a page.
assert iterator.num_results == 0
+ await page_aiter.aclose()
@pytest.mark.asyncio
async def test__items_aiter(self):