feat: add support for python 3.13 (#696)

* feat: add support for python 3.13

* Add constraints file

* Avoid Python3.13 warning coroutine method 'aclose' was never awaited

* remove allow-prereleases: true

* exclude grpcio 1.67.0rc1

* add comment

* remove empty line

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* Update comment

Co-authored-by: ohmayr <omairnaveed@ymail.com>

---------

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
Co-authored-by: ohmayr <omairnaveed@ymail.com>
diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml
index 2d1193d..a82859e 100644
--- a/.github/workflows/unittest.yml
+++ b/.github/workflows/unittest.yml
@@ -19,6 +19,7 @@
           - "3.10"
           - "3.11"
           - "3.12"
+          - "3.13"
         exclude:
           - option: "_wo_grpc"
             python: 3.7
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 8d1475c..1a1f608 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -21,7 +21,7 @@
   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 @@
 
 - To run a single unit test::
 
-    $ nox -s unit-3.12 -- -k <name of test>
+    $ nox -s unit-3.13 -- -k <name of test>
 
 
   .. note::
@@ -203,6 +203,7 @@
 -  `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 @@
 .. _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 3dbd27d..ada6f33 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 @@
         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 @@
 
     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 d3c2a2b..a15df56 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 0000000..e69de29
--- /dev/null
+++ b/testing/constraints-3.13.txt
diff --git a/tests/asyncio/retry/test_retry_streaming_async.py b/tests/asyncio/retry/test_retry_streaming_async.py
index ffdf314..d0fd799 100644
--- a/tests/asyncio/retry/test_retry_streaming_async.py
+++ b/tests/asyncio/retry/test_retry_streaming_async.py
@@ -139,6 +139,7 @@
         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 @@
             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 @@
         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 @@
         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 @@
         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 d8bba93..63e26d0 100644
--- a/tests/asyncio/test_page_iterator_async.py
+++ b/tests/asyncio/test_page_iterator_async.py
@@ -110,6 +110,7 @@
         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 @@
 
         # 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):