Repository: GoogleCloudPlatform/cloud-sql-python-connector (python)

Issue:
Remove `declare_namespace` calls in package
Need to move off of using `pkg_resources.declare_namespace()` for package structure as it is deprecated.

```
DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('google.cloud.sql')`.
  Implementing implicit namespace packages (as specified in PEP 420) is preferred to `pkg_resources.declare_namespace`. See https://setuptools.pypa.io/en/latest/references/keywords.html#keyword-namespace-packages
```

Pull request:
feat: introduce compatibility with native namespace packages
Fixes #876 

Ref: https://github.com/googleapis/google-auth-library-python/pull/1205

Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 97aaa27..7a234e6 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -31,7 +31,7 @@ jobs:
     steps:
       - name: Remove PR Label
         if: "${{ github.event.action == 'labeled' && github.event.label.name == 'tests: run' }}"
-        uses: actions/github-script@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # v7.0.0
+        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           script: |
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index c5bc03f..5527706 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -32,7 +32,7 @@ jobs:
     steps:
       - name: Remove PR Label
         if: "${{ github.event.action == 'labeled' && github.event.label.name == 'tests: run' }}"
-        uses: actions/github-script@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # v7.0.0
+        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           script: |
diff --git a/.mypy.ini b/.mypy.ini
index ae55300..9556bcd 100644
--- a/.mypy.ini
+++ b/.mypy.ini
@@ -2,6 +2,7 @@
 python_version = 3.8
 warn_unused_configs = True
 plugins = sqlmypy
+namespace_packages = True
 
 [mypy-google.auth.*]
 ignore_missing_imports = True
diff --git a/google/__init__.py b/google/__init__.py
deleted file mode 100644
index 868db78..0000000
--- a/google/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2019 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.
-try:
-    import pkg_resources
-
-    pkg_resources.declare_namespace(__name__)
-except ImportError:
-    import pkgutil
-
-    __path__ = pkgutil.extend_path(__path__, __name__)
diff --git a/google/cloud/__init__.py b/google/cloud/__init__.py
deleted file mode 100644
index 868db78..0000000
--- a/google/cloud/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2019 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.
-try:
-    import pkg_resources
-
-    pkg_resources.declare_namespace(__name__)
-except ImportError:
-    import pkgutil
-
-    __path__ = pkgutil.extend_path(__path__, __name__)
diff --git a/google/cloud/sql/__init__.py b/google/cloud/sql/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/google/cloud/sql/connector/__init__.py b/google/cloud/sql/connector/__init__.py
index 527e177..fe2e40d 100644
--- a/google/cloud/sql/connector/__init__.py
+++ b/google/cloud/sql/connector/__init__.py
@@ -13,17 +13,10 @@ 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.
 """
-from .connector import Connector, create_async_connector
-from .instance import IPTypes
 
+from google.cloud.sql.connector.connector import Connector, create_async_connector
+from google.cloud.sql.connector.instance import IPTypes
+from google.cloud.sql.connector.version import __version__
 
-__ALL__ = [create_async_connector, Connector, IPTypes]
 
-try:
-    import pkg_resources
-
-    pkg_resources.declare_namespace(__name__)
-except ImportError:
-    import pkgutil
-
-    __path__ = pkgutil.extend_path(__path__, __name__)
+__all__ = ["__version__", "create_async_connector", "Connector", "IPTypes"]
diff --git a/noxfile.py b/noxfile.py
index dcffa67..05c25c5 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -42,7 +42,7 @@ def lint(session):
         "google",
         "tests",
     )
-    session.run("mypy", "google", "tests")
+    session.run("mypy", "-p", "google", "--show-traceback")
     session.run("python", "setup.py", "sdist")
     session.run("twine", "check", "dist/*")
 
diff --git a/setup.py b/setup.py
index 7154901..8a70025 100644
--- a/setup.py
+++ b/setup.py
@@ -13,20 +13,8 @@
 # limitations under the License.
 import io
 import os
-from setuptools import setup, find_packages
 
-package_root = os.path.abspath(os.path.dirname(__file__))
-
-readme_filename = os.path.join(package_root, "README.md")
-with io.open(readme_filename, encoding="utf-8") as readme_file:
-    readme = readme_file.read()
-
-packages = [package for package in find_packages() if package.startswith("google")]
-
-# Determine which namespaces are needed.
-namespaces = ["google"]
-if "google.cloud" in packages:
-    namespaces.append("google.cloud")
+from setuptools import find_namespace_packages, setup
 
 name = "cloud-sql-python-connector"
 description = (
@@ -35,20 +23,32 @@ description = (
     " permissions to connect to a Cloud SQL database without having"
     " to manually allowlist IPs or manage SSL certificates."
 )
-
-version = {}
-with open("google/cloud/sql/connector/version.py") as fp:
-    exec(fp.read(), version)
-version = version["__version__"]
-
 release_status = "Development Status :: 5 - Production/Stable"
-core_dependencies = [
+dependencies = [
     "aiohttp",
     "cryptography>=38.0.3",
     "Requests",
     "google-auth",
 ]
 
+package_root = os.path.abspath(os.path.dirname(__file__))
+
+readme_filename = os.path.join(package_root, "README.md")
+with io.open(readme_filename, encoding="utf-8") as readme_file:
+    readme = readme_file.read()
+
+version = {}
+with open(os.path.join(package_root, "google/cloud/sql/connector/version.py")) as fp:
+    exec(fp.read(), version)
+version = version["__version__"]
+
+# Only include packages under the 'google' namespace. Do not include tests,
+# samples, etc.
+packages = [
+    package for package in find_namespace_packages() if package.startswith("google")
+]
+
+
 setup(
     name=name,
     version=version,
@@ -71,13 +71,12 @@ setup(
     ],
     platforms="Posix; MacOS X; Windows",
     packages=packages,
-    namespace_packages=namespaces,
-    install_requires=core_dependencies,
+    install_requires=dependencies,
     extras_require={
         "pymysql": ["PyMySQL>=1.1.0"],
         "pg8000": ["pg8000>=1.30.3"],
         "pytds": ["python-tds>=1.13.0"],
-        "asyncpg": ["asyncpg>=0.29.0"]
+        "asyncpg": ["asyncpg>=0.29.0"],
     },
     python_requires=">=3.8",
     include_package_data=True,


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 8bb4fa8..0b6d04a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -45,7 +45,7 @@ jobs:
     steps:
       - name: Remove PR label
         if: "${{ github.event.action == 'labeled' && github.event.label.name == 'tests: run' }}"
-        uses: actions/github-script@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # v7.0.0
+        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           script: |
@@ -162,7 +162,7 @@ jobs:
     steps:
       - name: Remove PR label
         if: "${{ github.event.action == 'labeled' && github.event.label.name == 'tests: run' }}"
-        uses: actions/github-script@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # v7.0.0
+        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           script: |
diff --git a/requirements-test.txt b/requirements-test.txt
index 1029c00..1835463 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -9,7 +9,6 @@ flake8-annotations==2.9.1
 black==23.11.0
 mypy==0.982
 sqlalchemy-stubs==0.4
-types-pkg-resources==0.1.3
 types-PyMySQL==1.1.0.1
 types-mock==5.1.0.2
 twine==4.0.2
diff --git a/tests/unit/test_packaging.py b/tests/unit/test_packaging.py
new file mode 100644
index 0000000..d0611f1
--- /dev/null
+++ b/tests/unit/test_packaging.py
@@ -0,0 +1,47 @@
+# 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 pathlib
+import subprocess
+import sys
+
+
+def test_namespace_package_compat(tmp_path: pathlib.PosixPath) -> None:
+    # The ``google`` namespace package should not be masked
+    # by the presence of ``cloud-sql-python-connector``.
+    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)
+
+    # The ``google.cloud`` namespace package should not be masked
+    # by the presence of ``cloud-sql-python-connector``.
+    google_cloud = tmp_path / "google" / "cloud"
+    google_cloud.mkdir()
+    google_cloud.joinpath("othermod.py").write_text("")
+    env = dict(os.environ, PYTHONPATH=str(tmp_path))
+    cmd = [sys.executable, "-m", "google.cloud.othermod"]
+    subprocess.check_call(cmd, env=env)
+
+    # The ``google.cloud.sql`` namespace package should not be masked
+    # by the presence of ``cloud-sql-python-connector``.
+    google_cloud_sql = tmp_path / "google" / "cloud" / "sql"
+    google_cloud_sql.mkdir()
+    google_cloud_sql.joinpath("othermod.py").write_text("")
+    env = dict(os.environ, PYTHONPATH=str(tmp_path))
+    cmd = [sys.executable, "-m", "google.cloud.sql.othermod"]
+    subprocess.check_call(cmd, env=env)