Skip to content

Commit f8b581e

Browse files
committed
fix lint issues
1 parent c9ad3bd commit f8b581e

File tree

6 files changed

+80
-57
lines changed

6 files changed

+80
-57
lines changed

airbyte_cdk/test/declarative/test_suites/connector_base.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,23 @@
44
from __future__ import annotations
55

66
import abc
7-
import functools
8-
import inspect
9-
import sys
107
from pathlib import Path
11-
from typing import Any, Callable, Literal
8+
from typing import Any, Literal
129

1310
import pytest
1411
import yaml
15-
from pydantic import BaseModel
16-
from typing_extensions import override
12+
from boltons.typeutils import classproperty
1713

1814
from airbyte_cdk import Connector
1915
from airbyte_cdk.models import (
2016
AirbyteMessage,
2117
Type,
2218
)
23-
from airbyte_cdk.sources import Source
24-
from airbyte_cdk.sources.declarative.declarative_source import (
25-
AbstractSource,
26-
DeclarativeSource,
27-
)
2819
from airbyte_cdk.test import entrypoint_wrapper
2920
from airbyte_cdk.test.declarative.models import (
3021
ConnectorTestScenario,
3122
)
32-
from airbyte_cdk.test.declarative.utils.job_runner import run_test_job
23+
from airbyte_cdk.test.declarative.utils.job_runner import IConnector, run_test_job
3324

3425
ACCEPTANCE_TEST_CONFIG = "acceptance-test-config.yml"
3526

@@ -49,7 +40,7 @@ class RunnableConnector(abc.ABC):
4940
def launch(cls, args: list[str] | None) -> None: ...
5041

5142

52-
def generate_tests(metafunc) -> None:
43+
def generate_tests(metafunc: pytest.Metafunc) -> None:
5344
"""
5445
A helper for pytest_generate_tests hook.
5546
@@ -111,8 +102,9 @@ class ConnectorTestSuiteBase(abc.ABC):
111102

112103
@classmethod
113104
def create_connector(
114-
cls, scenario: ConnectorTestScenario
115-
) -> Source | AbstractSource | DeclarativeSource | RunnableConnector:
105+
cls,
106+
scenario: ConnectorTestScenario,
107+
) -> IConnector:
116108
"""Instantiate the connector class."""
117109
raise NotImplementedError("Subclasses must implement this method.")
118110

@@ -121,7 +113,7 @@ def run_test_scenario(
121113
verb: Literal["read", "check", "discover"],
122114
test_scenario: ConnectorTestScenario,
123115
*,
124-
catalog: dict | None = None,
116+
catalog: dict[str, Any] | None = None,
125117
) -> entrypoint_wrapper.EntrypointOutput:
126118
"""Run a test job from provided CLI args and return the result."""
127119
return run_test_job(
@@ -146,11 +138,10 @@ def test_check(
146138
msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS
147139
] # noqa: SLF001 # Non-public API
148140
assert len(conn_status_messages) == 1, (
149-
"Expected exactly one CONNECTION_STATUS message. Got: \n" + "\n".join(result._messages)
141+
f"Expected exactly one CONNECTION_STATUS message. Got: {result._messages}"
150142
)
151143

152-
@classmethod
153-
@property
144+
@classproperty
154145
def acceptance_test_config_path(self) -> Path:
155146
"""Get the path to the acceptance test config file.
156147

airbyte_cdk/test/declarative/test_suites/source_base.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ def test_check(
4545
conn_status_messages: list[AirbyteMessage] = [
4646
msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS
4747
] # noqa: SLF001 # Non-public API
48-
assert len(conn_status_messages) == 1, (
49-
"Expected exactly one CONNECTION_STATUS message. Got: \n" + "\n".join(result._messages)
48+
num_status_messages = len(conn_status_messages)
49+
assert num_status_messages == 1, (
50+
f"Expected exactly one CONNECTION_STATUS message. Got {num_status_messages}: \n"
51+
+ "\n".join([str(m) for m in result._messages])
5052
)
5153

5254
def test_basic_read(
@@ -70,7 +72,7 @@ def test_basic_read(
7072
sync_mode=SyncMode.full_refresh,
7173
destination_sync_mode=DestinationSyncMode.append_dedup,
7274
)
73-
for stream in discover_result.catalog.catalog.streams
75+
for stream in discover_result.catalog.catalog.streams # type: ignore [reportOptionalMemberAccess]
7476
]
7577
)
7678
result = run_test_job(
@@ -102,8 +104,8 @@ def test_fail_with_bad_catalog(
102104
},
103105
supported_sync_modes=[SyncMode.full_refresh],
104106
),
105-
sync_mode="INVALID",
106-
destination_sync_mode="INVALID",
107+
sync_mode="INVALID", # type: ignore [reportArgumentType]
108+
destination_sync_mode="INVALID", # type: ignore [reportArgumentType]
107109
)
108110
]
109111
)

airbyte_cdk/test/declarative/utils/job_runner.py

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,56 @@
11
import tempfile
22
import uuid
3+
from dataclasses import asdict
34
from pathlib import Path
45
from typing import Any, Callable, Literal
56

67
import orjson
8+
from typing_extensions import Protocol, runtime_checkable
79

8-
from airbyte_cdk import Connector
910
from airbyte_cdk.models import (
11+
ConfiguredAirbyteCatalog,
1012
Status,
1113
)
12-
from airbyte_cdk.sources.abstract_source import AbstractSource
13-
from airbyte_cdk.sources.declarative.declarative_source import DeclarativeSource
1414
from airbyte_cdk.test import entrypoint_wrapper
1515
from airbyte_cdk.test.declarative.models import (
1616
ConnectorTestScenario,
1717
)
1818

1919

20+
@runtime_checkable
21+
class IConnector(Protocol):
22+
"""A connector that can be run in a test scenario."""
23+
24+
def launch(self, args: list[str] | None) -> None:
25+
"""Launch the connector with the given arguments."""
26+
...
27+
28+
2029
def run_test_job(
21-
connector: Connector | type[Connector] | Callable[[], Connector],
30+
connector: IConnector | type[IConnector] | Callable[[], IConnector],
2231
verb: Literal["read", "check", "discover"],
2332
test_instance: ConnectorTestScenario,
2433
*,
25-
catalog: dict[str, Any] | None = None,
34+
catalog: ConfiguredAirbyteCatalog | dict[str, Any] | None = None,
2635
) -> entrypoint_wrapper.EntrypointOutput:
2736
"""Run a test job from provided CLI args and return the result."""
2837
if not connector:
2938
raise ValueError("Connector is required")
3039

31-
connector_obj: Connector
32-
if isinstance(connector, type):
40+
if catalog and isinstance(catalog, ConfiguredAirbyteCatalog):
41+
# Convert the catalog to a dict if it's already a ConfiguredAirbyteCatalog.
42+
catalog = asdict(catalog)
43+
44+
connector_obj: IConnector
45+
if isinstance(connector, type) or callable(connector):
46+
# If the connector is a class or a factory lambda, instantiate it.
3347
connector_obj = connector()
34-
elif isinstance(connector, Connector):
35-
connector_obj = connector
36-
elif isinstance(connector, DeclarativeSource | AbstractSource):
48+
elif isinstance(connector, IConnector):
3749
connector_obj = connector
38-
elif isinstance(connector, Callable):
39-
try:
40-
connector_obj = connector()
41-
except Exception as ex:
42-
if not test_instance.expect_exception:
43-
raise
44-
45-
return entrypoint_wrapper.EntrypointOutput(
46-
messages=[],
47-
uncaught_exception=ex,
48-
)
4950
else:
50-
raise ValueError(f"Invalid source type: {type(connector)}")
51+
raise ValueError(
52+
f"Invalid connector input: {type(connector)}",
53+
)
5154

5255
args: list[str] = [verb]
5356
if test_instance.config_path:
@@ -82,34 +85,40 @@ def run_test_job(
8285
# Because it *also* can fail, we have ot redundantly wrap it in a try/except block.
8386

8487
result: entrypoint_wrapper.EntrypointOutput = entrypoint_wrapper._run_command( # noqa: SLF001 # Non-public API
85-
source=connector_obj,
88+
source=connector_obj, # type: ignore [reportArgumentType]
8689
args=args,
8790
expecting_exception=test_instance.expect_exception,
8891
)
8992
if result.errors and not test_instance.expect_exception:
9093
raise AssertionError(
9194
"\n\n".join(
92-
[str(err.trace.error).replace("\\n", "\n") for err in result.errors],
95+
[str(err.trace.error).replace("\\n", "\n") for err in result.errors if err.trace],
9396
)
9497
)
9598

9699
if verb == "check":
97100
# Check is expected to fail gracefully without an exception.
98101
# Instead, we assert that we have a CONNECTION_STATUS message with
99102
# a failure status.
100-
assert not result.errors, "Expected no errors from check. Got:\n" + "\n".join(
101-
[str(error) for error in result.errors]
102-
)
103+
assert not result.errors, "Expected no errors from check. Got:\n" + "\n".join([
104+
str(error) for error in result.errors
105+
])
103106
assert len(result.connection_status_messages) == 1, (
104107
"Expected exactly one CONNECTION_STATUS message. Got "
105108
f"{len(result.connection_status_messages)}:\n"
106109
+ "\n".join([str(msg) for msg in result.connection_status_messages])
107110
)
108111
if test_instance.expect_exception:
109-
assert result.connection_status_messages[0].connectionStatus.status == Status.FAILED, (
112+
conn_status = result.connection_status_messages[0].connectionStatus
113+
assert conn_status, (
114+
"Expected CONNECTION_STATUS message to be present. Got: \n"
115+
+ "\n".join([str(msg) for msg in result.connection_status_messages])
116+
)
117+
assert conn_status.status == Status.FAILED, (
110118
"Expected CONNECTION_STATUS message to be FAILED. Got: \n"
111119
+ "\n".join([str(msg) for msg in result.connection_status_messages])
112120
)
121+
113122
return result
114123

115124
# For all other verbs, we assert check that an exception is raised (or not).
@@ -121,7 +130,14 @@ def run_test_job(
121130
if result.errors:
122131
raise AssertionError(
123132
"\n\n".join(
124-
[str(err.trace.error).replace("\\n", "\n") for err in result.errors],
133+
[
134+
str(err.trace.error).replace(
135+
"\\n",
136+
"\n",
137+
)
138+
for err in result.errors
139+
if err.trace
140+
],
125141
)
126142
)
127143

airbyte_cdk/test/pytest_config/plugin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""Global pytest configuration for the Airbyte CDK tests."""
22

33
from pathlib import Path
4-
from typing import Optional
4+
from typing import cast
55

66
import pytest
77

88

9-
def pytest_collect_file(parent: Optional[pytest.Module], path: Path) -> pytest.Module | None:
9+
def pytest_collect_file(parent: pytest.Module | None, path: Path) -> pytest.Module | None:
1010
"""Collect test files based on their names."""
1111
if path.name == "test_connector.py":
1212
return cast(pytest.Module, pytest.Module.from_parent(parent, path=path))
@@ -41,6 +41,6 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
4141
print(f"Setting up test: {item.name}")
4242

4343

44-
def pytest_runtest_teardown(item: pytest.Item, nextitem: Optional[pytest.Item]) -> None:
44+
def pytest_runtest_teardown(item: pytest.Item, nextitem: pytest.Item | None) -> None:
4545
# This hook is called after each test function is executed
4646
print(f"Tearing down test: {item.name}")

poetry.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ sqlalchemy = {version = "^2.0,!=2.0.36", optional = true }
8383
xmltodict = ">=0.13,<0.15"
8484
anyascii = "^0.3.2"
8585
whenever = "^0.6.16"
86+
boltons = "^25.0.0"
8687

8788
[tool.poetry.group.dev.dependencies]
8889
freezegun = "*"

0 commit comments

Comments
 (0)