From fdcceeb740de6f19293ff07d778ffffe859aa217 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 14 Nov 2023 15:50:54 +0000 Subject: [PATCH] feat: Introduce compatibility with native namespace packages --- google/__init__.py | 22 ----------- google/cloud/__init__.py | 22 ----------- google/cloud/sqlalchemy_spanner/__init__.py | 5 ++- .../sqlalchemy_spanner/sqlalchemy_spanner.py | 5 +-- .../cloud/sqlalchemy_spanner/version.py | 0 setup.py | 30 +++++++-------- test/test_suite_13.py | 8 ++-- test/test_suite_14.py | 9 ++--- test/test_suite_20.py | 11 ++---- test/unit/test_packaging.py | 37 +++++++++++++++++++ 10 files changed, 68 insertions(+), 81 deletions(-) delete mode 100644 google/__init__.py delete mode 100644 google/cloud/__init__.py rename version.py => google/cloud/sqlalchemy_spanner/version.py (100%) create mode 100644 test/unit/test_packaging.py diff --git a/google/__init__.py b/google/__init__.py deleted file mode 100644 index e0bdef06..00000000 --- a/google/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2021 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 e0bdef06..00000000 --- a/google/cloud/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2021 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/sqlalchemy_spanner/__init__.py b/google/cloud/sqlalchemy_spanner/__init__.py index 6cfd02bb..a5a387d1 100644 --- a/google/cloud/sqlalchemy_spanner/__init__.py +++ b/google/cloud/sqlalchemy_spanner/__init__.py @@ -14,4 +14,7 @@ from .sqlalchemy_spanner import SpannerDialect -__all__ = (SpannerDialect,) +from .version import __version__ + + +__all__ = (SpannerDialect, __version__) diff --git a/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py b/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py index 7f0b44a9..c450354c 100644 --- a/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py +++ b/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pkg_resources import re from alembic.ddl.base import ( @@ -45,6 +44,7 @@ from google.cloud.spanner_v1.data_types import JsonObject from google.cloud import spanner_dbapi from google.cloud.sqlalchemy_spanner._opentelemetry_tracing import trace_call +from google.cloud.sqlalchemy_spanner import __version__ as sqlalchemy_spanner_version import sqlalchemy USING_SQLACLCHEMY_20 = False @@ -701,10 +701,9 @@ def create_connect_args(self, url): ), url.database, ) - dist = pkg_resources.get_distribution("sqlalchemy-spanner") return ( [match.group("instance"), match.group("database"), match.group("project")], - {"user_agent": f"gl-{dist.project_name}/{dist.version}"}, + {"user_agent": f"gl-sqlalchemy-spanner/{sqlalchemy_spanner_version}"}, ) @engine_to_connection diff --git a/version.py b/google/cloud/sqlalchemy_spanner/version.py similarity index 100% rename from version.py rename to google/cloud/sqlalchemy_spanner/version.py diff --git a/setup.py b/setup.py index 72fa5653..d05dd444 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ import io import os +import re import setuptools @@ -34,14 +35,17 @@ ] } -BASE_DIR = os.path.dirname(__file__) -VERSION_FILENAME = os.path.join(BASE_DIR, "version.py") -PACKAGE_INFO = {} -with open(VERSION_FILENAME) as f: - exec(f.read(), PACKAGE_INFO) -version = PACKAGE_INFO["__version__"] - package_root = os.path.abspath(os.path.dirname(__file__)) + +version = None + +with open( + os.path.join(package_root, "google/cloud/sqlalchemy_spanner/version.py") +) as fp: + version_candidates = re.findall(r"(?<=\")\d+.\d+.\d+(?=\")", fp.read()) + assert len(version_candidates) == 1 + version = version_candidates[0] + readme_filename = os.path.join(package_root, "README.rst") with io.open(readme_filename, encoding="utf-8") as readme_file: readme = readme_file.read() @@ -50,18 +54,13 @@ # benchmarks, etc. packages = [ package - for package in setuptools.PEP420PackageFinder.find() + 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( author="Google LLC", - author_email="cloud-spanner-developers@googlegroups.com", + author_email="googleapis-packages@google.com", classifiers=["Intended Audience :: Developers"], description=description, long_description=readme, @@ -73,9 +72,8 @@ install_requires=dependencies, extras_require=extras, name=name, - namespace_packages=namespaces, packages=packages, - url="https://github.com/cloudspannerecosystem/python-spanner-sqlalchemy", + url="https://github.com/googleapis/python-spanner-sqlalchemy", version=version, include_package_data=True, zip_safe=False, diff --git a/test/test_suite_13.py b/test/test_suite_13.py index ca11979b..9c56752f 100644 --- a/test/test_suite_13.py +++ b/test/test_suite_13.py @@ -18,7 +18,6 @@ import decimal import operator import os -import pkg_resources import pytest import random import time @@ -126,6 +125,8 @@ ) from test._helpers import get_db_url, get_project +from google.cloud.sqlalchemy_spanner import __version__ as sqlalchemy_spanner_version + config.test_schema = "" @@ -1645,12 +1646,10 @@ class UserAgentTest(SpannerSpecificTestBase): """Check that SQLAlchemy dialect uses correct user agent.""" def test_user_agent(self): - dist = pkg_resources.get_distribution("sqlalchemy-spanner") - with self._engine.connect() as connection: assert ( connection.connection.instance._client._client_info.user_agent - == f"gl-{dist.project_name}/{dist.version}" + == f"gl-sqlalchemy-spanner/{sqlalchemy_spanner_version}" ) @@ -1702,7 +1701,6 @@ def setUp(self): def test_offset_only(self): for offset in [1, 7, 10, 100, 1000, 10000]: - with self._engine.connect().execution_options(read_only=True) as connection: list(connection.execute(self._table.select().offset(offset)).fetchall()) diff --git a/test/test_suite_14.py b/test/test_suite_14.py index 87437b83..20fe24da 100644 --- a/test/test_suite_14.py +++ b/test/test_suite_14.py @@ -18,7 +18,6 @@ import decimal import operator import os -import pkg_resources import pytest import random import time @@ -142,6 +141,9 @@ ) from test._helpers import get_db_url, get_project +from google.cloud.sqlalchemy_spanner import __version__ as sqlalchemy_spanner_version + + config.test_schema = "" @@ -556,7 +558,6 @@ def test_get_foreign_keys(self, connection, use_schema): def test_get_table_names( self, connection, order_by, include_plain, include_views, use_schema ): - if use_schema: schema = config.test_schema else: @@ -1913,12 +1914,10 @@ def setUp(self): self._metadata = MetaData(bind=self._engine) def test_user_agent(self): - dist = pkg_resources.get_distribution("sqlalchemy-spanner") - with self._engine.connect() as connection: assert ( connection.connection.instance._client._client_info.user_agent - == "gl-" + dist.project_name + "/" + dist.version + == f"gl-sqlalchemy-spanner/{sqlalchemy_spanner_version}" ) diff --git a/test/test_suite_20.py b/test/test_suite_20.py index 50958aaa..4347c539 100644 --- a/test/test_suite_20.py +++ b/test/test_suite_20.py @@ -18,7 +18,6 @@ import decimal import operator import os -import pkg_resources import pytest import random import time @@ -151,6 +150,9 @@ ) # noqa: F401, F403 from test._helpers import get_db_url, get_project +from google.cloud.sqlalchemy_spanner import __version__ as sqlalchemy_spanner_version + + config.test_schema = "" @@ -872,7 +874,6 @@ def test_get_multi_foreign_keys( scope=ObjectScope.DEFAULT, kind=ObjectKind.TABLE, ): - """ SPANNER OVERRIDE: @@ -1088,7 +1089,6 @@ def test_get_foreign_keys(self, connection, use_schema): (True, testing.requires.schemas), False, argnames="use_schema" ) def test_get_table_names(self, connection, order_by, use_schema): - schema = None _ignore_tables = [ @@ -1977,7 +1977,6 @@ def _round_trip(self, datatype, data): assert isinstance(row[0], (long, int)) # noqa def _huge_ints(): - return testing.combinations( 2147483649, # 32 bits 2147483648, # 32 bits @@ -2653,12 +2652,10 @@ def setUp(self): self._metadata = MetaData() def test_user_agent(self): - dist = pkg_resources.get_distribution("sqlalchemy-spanner") - with self._engine.connect() as connection: assert ( connection.connection.instance._client._client_info.user_agent - == "gl-" + dist.project_name + "/" + dist.version + == f"gl-sqlalchemy-spanner/{sqlalchemy_spanner_version}" ) diff --git a/test/unit/test_packaging.py b/test/unit/test_packaging.py new file mode 100644 index 00000000..c05f6042 --- /dev/null +++ b/test/unit/test_packaging.py @@ -0,0 +1,37 @@ +# 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 ``sqlalchemy-spanner``. + 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 ``sqlalchemy-spanner``. + 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)