diff --git a/.kokoro/release.sh b/.kokoro/release.sh index c03a31f0..6d767c28 100755 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -18,7 +18,7 @@ python3 -m pip install --upgrade twine wheel setuptools export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_GFILE_DIR}/secret_manager/google-cloud-pypi-token") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") cd github/python-spanner-sqlalchemy python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg index 9a818a87..92607a27 100644 --- a/.kokoro/release/common.cfg +++ b/.kokoro/release/common.cfg @@ -23,8 +23,18 @@ env_vars: { value: "github/python-spanner-sqlalchemy/.kokoro/release.sh" } +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google-cloud-pypi-token-keystore-1" + } + } +} + # Tokens needed to report release status back to GitHub env_vars: { key: "SECRET_MANAGER_KEYS" - value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem,google-cloud-pypi-token" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a42c9b..39c469bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.2.0](https://github.com/googleapis/python-spanner-sqlalchemy/compare/v1.1.0...v1.2.0) (2022-06-03) + + +### Features + +* allow SQLAlchemy 1.4 use ([#198](https://github.com/googleapis/python-spanner-sqlalchemy/issues/198)) ([7793b7d](https://github.com/googleapis/python-spanner-sqlalchemy/commit/7793b7ddfcbd99e966e3ef6f7ec13d7dc04d39fb)) + ## [1.1.0](https://github.com/googleapis/python-spanner-sqlalchemy/compare/v1.0.0...v1.1.0) (2022-01-28) diff --git a/google/cloud/sqlalchemy_spanner/requirements.py b/google/cloud/sqlalchemy_spanner/requirements.py index d552dc34..ce5e8d53 100644 --- a/google/cloud/sqlalchemy_spanner/requirements.py +++ b/google/cloud/sqlalchemy_spanner/requirements.py @@ -29,6 +29,7 @@ def computed_columns(self): def computed_columns_stored(self): return exclusions.open() + @property def sane_rowcount(self): return exclusions.closed() diff --git a/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py b/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py index 9a1209b2..82eb14bd 100644 --- a/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py +++ b/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py @@ -48,7 +48,9 @@ @listens_for(Pool, "reset") def reset_connection(dbapi_conn, connection_record): """An event of returning a connection back to a pool.""" - dbapi_conn.connection.rollback() + if dbapi_conn.connection.inside_transaction: + dbapi_conn.connection.rollback() + if getattr(dbapi_conn.connection, "staleness", None) is not None: dbapi_conn.connection.staleness = None diff --git a/noxfile.py b/noxfile.py index fb9f923d..29d4f20c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -68,7 +68,7 @@ class = StreamHandler )""" -BLACK_VERSION = "black==19.10b0" +BLACK_VERSION = "black==22.3.0" BLACK_PATHS = ["google", "test", "noxfile.py", "setup.py", "samples"] DEFAULT_PYTHON_VERSION = "3.8" @@ -82,10 +82,15 @@ def lint(session): """ session.install("flake8", BLACK_VERSION) session.run( - "black", "--check", *BLACK_PATHS, + "black", + "--check", + *BLACK_PATHS, ) session.run( - "flake8", "google", "test", "--max-line-length=88", + "flake8", + "google", + "test", + "--max-line-length=88", ) @@ -101,7 +106,8 @@ def blacken(session): """ session.install(BLACK_VERSION) session.run( - "black", *BLACK_PATHS, + "black", + *BLACK_PATHS, ) @@ -128,17 +134,23 @@ def compliance_test_13(session): ) session.install( - "pytest", "pytest-cov", "pytest-asyncio", + "pytest", + "pytest-cov", + "pytest-asyncio", ) session.install("mock") session.install("-e", ".[tracing]") + session.run("pip", "install", "sqlalchemy>=1.1.13,<=1.3.24", "--force-reinstall") + session.run("pip", "install", "pytest==6.2.2", "--force-reinstall") + session.run("pip", "install", "opentelemetry-api<=1.10", "--force-reinstall") + session.run("pip", "install", "opentelemetry-sdk<=1.10", "--force-reinstall") session.run("python", "create_test_database.py") session.run( "py.test", "--cov=google.cloud.sqlalchemy_spanner", - "--cov=tests", + "--cov=test", "--cov-append", "--cov-config=.coveragerc", "--cov-report=", @@ -164,7 +176,9 @@ def compliance_test_14(session): ) session.install( - "pytest", "pytest-cov", "pytest-asyncio", + "pytest", + "pytest-cov", + "pytest-asyncio", ) session.install("mock") @@ -176,7 +190,7 @@ def compliance_test_14(session): session.run( "py.test", "--cov=google.cloud.sqlalchemy_spanner", - "--cov=tests", + "--cov=test", "--cov-append", "--cov-config=.coveragerc", "--cov-report=", @@ -214,7 +228,8 @@ def migration_test(session): session.run("python", "create_test_database.py") project = os.getenv( - "GOOGLE_CLOUD_PROJECT", os.getenv("PROJECT_ID", "emulator-test-project"), + "GOOGLE_CLOUD_PROJECT", + os.getenv("PROJECT_ID", "emulator-test-project"), ) db_url = ( f"spanner+spanner:///projects/{project}/instances/" @@ -242,7 +257,9 @@ def migration_test(session): with open(files[0], "r") as f: script_code = f.read() - script_code = script_code.replace("""def upgrade():\n pass""", UPGRADE_CODE) + script_code = script_code.replace( + """def upgrade() -> None:\n pass""", UPGRADE_CODE + ) with open(files[0], "w") as f: f.write(script_code) diff --git a/samples/conftest.py b/samples/conftest.py index 5a4f622e..298d8160 100644 --- a/samples/conftest.py +++ b/samples/conftest.py @@ -33,7 +33,8 @@ @pytest.fixture def db_url(): project = os.getenv( - "GOOGLE_CLOUD_PROJECT", os.getenv("PROJECT_ID", "emulator-test-project"), + "GOOGLE_CLOUD_PROJECT", + os.getenv("PROJECT_ID", "emulator-test-project"), ) db_url = ( f"spanner:///projects/{project}/instances/" diff --git a/setup.cfg b/setup.cfg index fd3202c4..4a1e77d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ [tool:pytest] addopts= --tb native -v -r fxX --maxfail=25 -p no:warnings -python_files=test/*test_*.py +python_classes=*Test [sqla_testing] requirement_cls=google.cloud.sqlalchemy_spanner.requirements:Requirements diff --git a/setup.py b/setup.py index 2f1be807..9f89d405 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ name = "sqlalchemy-spanner" description = "SQLAlchemy dialect integrated into Cloud Spanner database" dependencies = [ - "sqlalchemy>=1.1.13, <=1.3.23", + "sqlalchemy>=1.1.13", "google-cloud-spanner>=3.12.0", "alembic", ] diff --git a/test/_helpers.py b/test/_helpers.py index dd18a149..8ef795f2 100644 --- a/test/_helpers.py +++ b/test/_helpers.py @@ -32,7 +32,8 @@ PROJECT = os.getenv( - "GOOGLE_CLOUD_PROJECT", os.getenv("PROJECT_ID", "emulator-test-project"), + "GOOGLE_CLOUD_PROJECT", + os.getenv("PROJECT_ID", "emulator-test-project"), ) DB_URL = ( f"spanner:///projects/{PROJECT}/instances/" diff --git a/test/benchmark.py b/test/benchmark.py index 0ff50ae5..0260ffc0 100644 --- a/test/benchmark.py +++ b/test/benchmark.py @@ -154,7 +154,7 @@ def __init__(self): self._many_rows = [] self._many_rows2 = [] birth_date = datetime.date(1998, 10, 6) - picture = base64.b64encode(u"123".encode()) + picture = base64.b64encode("123".encode()) for num in self._many_rows_ids: self._many_rows.append( { @@ -216,7 +216,9 @@ def __init__(self): super().__init__() self._engine = create_engine( "spanner:///projects/{project}/instances/{instance}/databases/{db}".format( - project=PROJECT, instance=INSTANCE, db=DATABASE, + project=PROJECT, + instance=INSTANCE, + db=DATABASE, ) ) metadata = MetaData(bind=self._engine) @@ -260,13 +262,15 @@ def insert_one_row_with_fetch_after(self): @measure_execution_time def insert_many_rows(self): self._conn.execute( - self._table.insert(), self._many_rows, + self._table.insert(), + self._many_rows, ) @measure_execution_time def insert_many_rows_with_mutations(self): self._conn.execute( - self._table.insert(), self._many_rows2, + self._table.insert(), + self._many_rows2, ) @measure_execution_time diff --git a/test/test_suite_13.py b/test/test_suite_13.py index b53045ac..7b53b82c 100644 --- a/test/test_suite_13.py +++ b/test/test_suite_13.py @@ -793,7 +793,10 @@ def _test_get_unique_constraints(self, schema=None): reflected_metadata = MetaData() reflected = Table( - "testtbl", reflected_metadata, autoload_with=orig_meta.bind, schema=schema, + "testtbl", + reflected_metadata, + autoload_with=orig_meta.bind, + schema=schema, ) # test "deduplicates for index" logic. MySQL and Oracle @@ -1079,7 +1082,9 @@ def test_render_literal_numeric(self): Overriding the test to avoid the same failure. """ self._literal_round_trip( - Numeric(precision=8, scale=4), [15.7563], [decimal.Decimal("15.7563")], + Numeric(precision=8, scale=4), + [15.7563], + [decimal.Decimal("15.7563")], ) self._literal_round_trip( Numeric(precision=8, scale=4), @@ -1098,7 +1103,9 @@ def test_render_literal_numeric_asfloat(self): Overriding the test to avoid the same failure. """ self._literal_round_trip( - Numeric(precision=8, scale=4, asdecimal=False), [15.7563], [15.7563], + Numeric(precision=8, scale=4, asdecimal=False), + [15.7563], + [15.7563], ) self._literal_round_trip( Numeric(precision=8, scale=4, asdecimal=False), @@ -1193,7 +1200,9 @@ def test_float_as_decimal(self): Overriding the test to avoid the same failure. """ self._do_test( - Float(precision=8, asdecimal=True), [15.7563], [decimal.Decimal("15.7563")], + Float(precision=8, asdecimal=True), + [15.7563], + [decimal.Decimal("15.7563")], ) self._do_test( @@ -1742,7 +1751,9 @@ def define_tables(cls, metadata): if testing.requires.computed_columns_stored.enabled: t.append_column( Column( - "computed_stored", Integer, Computed("normal - 42", persisted=True), + "computed_stored", + Integer, + Computed("normal - 42", persisted=True), ) ) if testing.requires.schemas.enabled: diff --git a/test/test_suite_14.py b/test/test_suite_14.py index 688d822e..6d708f08 100644 --- a/test/test_suite_14.py +++ b/test/test_suite_14.py @@ -530,7 +530,9 @@ def test_reflect_bytes_column_max_len(self): """ metadata = MetaData(self.bind) Table( - "bytes_table", metadata, Column("TestColumn", LargeBinary, nullable=False), + "bytes_table", + metadata, + Column("TestColumn", LargeBinary, nullable=False), ) metadata.create_all() @@ -600,7 +602,10 @@ def test_get_unique_constraints(self, metadata, connection, use_schema): reflected_metadata = MetaData() reflected = Table( - "testtbl", reflected_metadata, autoload_with=connection, schema=schema, + "testtbl", + reflected_metadata, + autoload_with=connection, + schema=schema, ) # test "deduplicates for index" logic. MySQL and Oracle @@ -964,7 +969,9 @@ def test_limit_render_multiple_times(self, connection): u = union(select(stmt), select(stmt)).subquery().select() self._assert_result( - connection, u, [(2,)], + connection, + u, + [(2,)], ) @@ -1939,7 +1946,9 @@ def define_tables(cls, metadata): if testing.requires.computed_columns_stored.enabled: t.append_column( Column( - "computed_stored", Integer, Computed("normal - 42", persisted=True), + "computed_stored", + Integer, + Computed("normal - 42", persisted=True), ) ) @@ -1960,11 +1969,17 @@ def test_get_column_returns_persisted_with_schema(self): ) if testing.requires.computed_columns_virtual.enabled: self.check_column( - data, "computed_virtual", "normal/2", False, + data, + "computed_virtual", + "normal/2", + False, ) if testing.requires.computed_columns_stored.enabled: self.check_column( - data, "computed_stored", "normal-42", True, + data, + "computed_stored", + "normal-42", + True, ) @pytest.mark.skip("Default values are not supported.") diff --git a/test/unit/test_opentelemetry_tracing.py b/test/unit/test_opentelemetry_tracing.py index e762f93d..a8af61c6 100644 --- a/test/unit/test_opentelemetry_tracing.py +++ b/test/unit/test_opentelemetry_tracing.py @@ -32,7 +32,7 @@ def _make_rpc_error(error_cls, trailing_metadata=None): # Skip all of these tests if we don't have OpenTelemetry if HAS_OPENTELEMETRY_INSTALLED: - class TestNoTracing(OpenTelemetryBase): + class NoTracingTest(OpenTelemetryBase): def setup(self): self._temp_opentelemetry = sys.modules["opentelemetry"] @@ -47,7 +47,7 @@ def test_no_trace_call(self): with _opentelemetry_tracing.trace_call("Test") as no_span: assert no_span is None - class TestTracing(OpenTelemetryBase): + class TracingTest(OpenTelemetryBase): def test_trace_call(self): extra_attributes = { "attribute1": "value1", @@ -96,7 +96,8 @@ def test_trace_error(self): with pytest.raises(GoogleAPICallError): with _opentelemetry_tracing.trace_call( - "CloudSpannerSqlAlchemy.Test", extra_attributes, + "CloudSpannerSqlAlchemy.Test", + extra_attributes, ) as span: from google.api_core.exceptions import InvalidArgument diff --git a/version.py b/version.py index 8d8f602b..3329f656 100644 --- a/version.py +++ b/version.py @@ -4,4 +4,4 @@ # license that can be found in the LICENSE file or at # https://developers.google.com/open-source/licenses/bsd -__version__ = "1.1.0" +__version__ = "1.2.0"