diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29432a002..89c372261 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ env: jobs: tests: - name: "Test on ${{ matrix.python-version }} (${{ matrix.session }}) / ${{ matrix.os }} / SQLAlchemy: ${{ matrix.sqlalchemy }}" + name: "Test on ${{ matrix.python-version }} (${{ matrix.session }}) / ${{ matrix.os }}" runs-on: ${{ matrix.os }} continue-on-error: true env: @@ -55,12 +55,10 @@ jobs: - "3.11" - "3.12" - "3.13" - sqlalchemy: ["2"] include: - - { session: tests, python-version: "3.13", os: "ubuntu-latest", sqlalchemy: "1" } - - { session: doctest, python-version: "3.13", os: "ubuntu-latest", sqlalchemy: "2" } - - { session: mypy, python-version: "3.12", os: "ubuntu-latest", sqlalchemy: "2" } - - { session: deps, python-version: "3.12", os: "ubuntu-latest", sqlalchemy: "2" } + - { session: doctest, python-version: "3.13", os: "ubuntu-latest" } + - { session: mypy, python-version: "3.12", os: "ubuntu-latest" } + - { session: deps, python-version: "3.12", os: "ubuntu-latest" } steps: - uses: actions/checkout@v4 @@ -94,7 +92,6 @@ jobs: - name: Run Nox env: - SQLALCHEMY_VERSION: ${{ matrix.sqlalchemy }} PIP_PRE: "1" UV_PRERELEASE: allow run: | diff --git a/noxfile.py b/noxfile.py index 3a94fe9fa..484d1bdd3 100644 --- a/noxfile.py +++ b/noxfile.py @@ -75,10 +75,6 @@ def tests(session: nox.Session) -> None: session.install(f".[{','.join(extras)}]") session.install(*test_dependencies) - sqlalchemy_version = os.environ.get("SQLALCHEMY_VERSION") - if sqlalchemy_version: - session.install(f"sqlalchemy=={sqlalchemy_version}.*") - env = {"COVERAGE_CORE": "sysmon"} if session.python == "3.12" else {} try: @@ -103,9 +99,6 @@ def benches(session: nox.Session) -> None: """Run benchmarks.""" session.install(".[jwt,s3]") session.install(*test_dependencies) - sqlalchemy_version = os.environ.get("SQLALCHEMY_VERSION") - if sqlalchemy_version: - session.install(f"sqlalchemy=={sqlalchemy_version}") session.run( "pytest", "--benchmark-only", diff --git a/singer_sdk/connectors/sql.py b/singer_sdk/connectors/sql.py index 87eb1eb98..98e6ea7d6 100644 --- a/singer_sdk/connectors/sql.py +++ b/singer_sdk/connectors/sql.py @@ -982,6 +982,28 @@ def prepare_table( self.to_sql_type(property_def), ) + self.prepare_primary_key( + full_table_name=full_table_name, + primary_keys=primary_keys, + ) + + def prepare_primary_key( + self, + *, + full_table_name: str | FullyQualifiedName, # noqa: ARG002 + primary_keys: t.Sequence[str], # noqa: ARG002 + ) -> None: + """Adapt target table primary key to provided schema if possible. + + Implement this method in a subclass to adapt the primary key of the target table + to the provided one if possible. + + Args: + full_table_name: the target table name. + primary_keys: list of key properties. + """ + self.logger.debug("Primary key adaptation is not implemented") + def prepare_column( self, full_table_name: str | FullyQualifiedName, diff --git a/singer_sdk/testing/suites.py b/singer_sdk/testing/suites.py index fd53e6e19..a61268759 100644 --- a/singer_sdk/testing/suites.py +++ b/singer_sdk/testing/suites.py @@ -36,6 +36,7 @@ TargetInvalidSchemaTest, TargetNoPrimaryKeys, TargetOptionalAttributes, + TargetPrimaryKeyUpdates, TargetRecordBeforeSchemaTest, TargetRecordMissingKeyProperty, TargetRecordMissingOptionalFields, @@ -104,6 +105,7 @@ class TestSuite(t.Generic[T]): # TargetMultipleStateMessages, TargetNoPrimaryKeys, TargetOptionalAttributes, + TargetPrimaryKeyUpdates, TargetRecordBeforeSchemaTest, TargetRecordMissingKeyProperty, TargetRecordMissingOptionalFields, diff --git a/singer_sdk/testing/target_test_streams/pk_updates.singer b/singer_sdk/testing/target_test_streams/pk_updates.singer new file mode 100644 index 000000000..c9b920599 --- /dev/null +++ b/singer_sdk/testing/target_test_streams/pk_updates.singer @@ -0,0 +1,4 @@ +{"type": "SCHEMA", "stream": "example_stream", "schema": {"properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "email": {"type": "string"}}, "key_properties": ["id"]}} +{"type": "RECORD", "stream": "example_stream", "record": {"id": 1, "name": "Alice", "email": "alice@example.com"}} +{"type": "SCHEMA", "stream": "example_stream", "schema": {"properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "email": {"type": "string"}}, "key_properties": ["email"]}} +{"type": "RECORD", "stream": "example_stream", "record": {"id": 2, "name": "Bob", "email": "bob@example.com"}} diff --git a/singer_sdk/testing/target_tests.py b/singer_sdk/testing/target_tests.py index 96e0b0d59..c9d5b94de 100644 --- a/singer_sdk/testing/target_tests.py +++ b/singer_sdk/testing/target_tests.py @@ -135,6 +135,12 @@ class TargetSchemaUpdates(TargetFileTestTemplate): name = "schema_updates" +class TargetPrimaryKeyUpdates(TargetFileTestTemplate): + """Test Target handles Primary Key updates.""" + + name = "pk_updates" + + class TargetSpecialCharsInAttributes(TargetFileTestTemplate): """Test Target handles special chars in attributes.""" diff --git a/tests/conftest.py b/tests/conftest.py index 0b5bc4b74..ae629dee8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,6 @@ import typing as t import pytest -import sqlalchemy as sa from singer_sdk import SQLConnector from singer_sdk import typing as th @@ -44,11 +43,6 @@ def pytest_runtest_setup(item): pytest.skip(f"cannot run on platform {system}") -def pytest_report_header() -> list[str]: - """Return a list of strings to be displayed in the header of the report.""" - return [f"sqlalchemy: {sa.__version__}"] - - @pytest.fixture(autouse=True) def _reset_envvars(monkeypatch: pytest.MonkeyPatch): """Remove envvars that might interfere with tests.""" diff --git a/tests/core/test_connector_sql.py b/tests/core/test_connector_sql.py index f76f30525..94bce926e 100644 --- a/tests/core/test_connector_sql.py +++ b/tests/core/test_connector_sql.py @@ -321,9 +321,7 @@ def test_column_rename(self, connector: DuckDBConnector): connector.rename_column("test_table", "old_name", "new_name") with engine.connect() as conn: - result = conn.execute( - sa.text("SELECT * FROM test_table"), - ) + result = conn.execute(sa.text("SELECT * FROM test_table")) assert result.keys() == ["id", "new_name"] def test_adapt_column_type(self, connector: DuckDBConnector): @@ -341,9 +339,7 @@ def test_adapt_column_type(self, connector: DuckDBConnector): connector._adapt_column_type("test_table", "name", sa.types.String()) with engine.connect() as conn: - result = conn.execute( - sa.text("SELECT * FROM test_table"), - ) + result = conn.execute(sa.text("SELECT * FROM test_table")) assert result.keys() == ["id", "name"] assert result.cursor.description[1][1] == "STRING"