Repository: googleapis/python-api-core
Pull request #561 (2023-11-30 21:31:25)

feat: Introduce compatibility with native namespace packages
Similar to https://github.com/googleapis/google-auth-library-python/pull/1205
Also see https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#native-namespace-packages


Patch:
diff --git a/google/__init__.py b/google/__init__.py
deleted file mode 100644
index 9f1d549..0000000
--- a/google/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2016 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Google namespace package."""
-
-try:
-    import pkg_resources
-
-    pkg_resources.declare_namespace(__name__)
-except ImportError:
-    import pkgutil
-
-    # See: https://github.com/python/mypy/issues/1422
-    __path__ = pkgutil.extend_path(__path__, __name__)  # type: ignore
diff --git a/google/api_core/bidi.py b/google/api_core/bidi.py
index 74abc49..17d836d 100644
--- a/google/api_core/bidi.py
+++ b/google/api_core/bidi.py
@@ -91,8 +91,12 @@ class _RequestQueueGenerator(object):
     def _is_active(self):
         # Note: there is a possibility that this starts *before* the call
         # property is set. So we have to check if self.call is set before
-        # seeing if it's active.
-        return self.call is not None and self.call.is_active()
+        # seeing if it's active. We need to return True if self.call is None.
+        # See https://github.com/googleapis/python-api-core/issues/560.
+        if self.call is not None and not self.call.is_active():
+            return False
+        else:
+            return True
 
     def __iter__(self):
         if self._initial_request is not None:
diff --git a/setup.py b/setup.py
index d4639a9..0c9ad22 100644
--- a/setup.py
+++ b/setup.py
@@ -63,15 +63,11 @@ with io.open(readme_filename, encoding="utf-8") as readme_file:
 # Only include packages under the 'google' namespace. Do not include tests,
 # benchmarks, etc.
 packages = [
-    package for package in setuptools.find_packages() if package.startswith("google")
+    package
+    for package in setuptools.find_namespace_packages()
+    if package.startswith("google")
 ]
 
-# Determine which namespaces are needed.
-namespaces = ["google"]
-if "google.cloud" in packages:
-    namespaces.append("google.cloud")
-
-
 setuptools.setup(
     name=name,
     version=version,
@@ -97,7 +93,6 @@ setuptools.setup(
     ],
     platforms="Posix; MacOS X; Windows",
     packages=packages,
-    namespace_packages=namespaces,
     install_requires=dependencies,
     extras_require=extras,
     python_requires=">=3.7",


Test patch:
diff --git a/tests/unit/test_packaging.py b/tests/unit/test_packaging.py
new file mode 100644
index 0000000..8100a49
--- /dev/null
+++ b/tests/unit/test_packaging.py
@@ -0,0 +1,28 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import subprocess
+import sys
+
+
+def test_namespace_package_compat(tmp_path):
+    # The ``google`` namespace package should not be masked
+    # by the presence of ``google-api-core``.
+    google = tmp_path / "google"
+    google.mkdir()
+    google.joinpath("othermod.py").write_text("")
+    env = dict(os.environ, PYTHONPATH=str(tmp_path))
+    cmd = [sys.executable, "-m", "google.othermod"]
+    subprocess.check_call(cmd, env=env)