airbyte-cdk 6.45.0.post24.dev14387467928__py3-none-any.whl → 6.45.1.post42.dev14452300548__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airbyte_cdk/connector_builder/main.py +3 -3
- airbyte_cdk/models/__init__.py +1 -0
- airbyte_cdk/models/airbyte_protocol.py +1 -3
- airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +1 -1
- airbyte_cdk/sources/concurrent_source/concurrent_source.py +3 -3
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py +8 -0
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +36 -0
- airbyte_cdk/sources/declarative/extractors/record_selector.py +6 -1
- airbyte_cdk/sources/declarative/interpolation/filters.py +49 -2
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +31 -0
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +39 -1
- airbyte_cdk/sources/declarative/retrievers/file_uploader.py +89 -0
- airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +9 -4
- airbyte_cdk/sources/file_based/file_based_source.py +3 -3
- airbyte_cdk/sources/file_based/file_based_stream_reader.py +38 -15
- airbyte_cdk/sources/file_based/file_record_data.py +22 -0
- airbyte_cdk/sources/file_based/file_types/avro_parser.py +1 -1
- airbyte_cdk/sources/file_based/file_types/file_transfer.py +8 -15
- airbyte_cdk/sources/file_based/schema_helpers.py +9 -1
- airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +6 -15
- airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +15 -38
- airbyte_cdk/sources/file_based/stream/permissions_file_based_stream.py +1 -3
- airbyte_cdk/sources/streams/concurrent/default_stream.py +3 -0
- airbyte_cdk/sources/types.py +11 -2
- airbyte_cdk/sources/utils/files_directory.py +15 -0
- airbyte_cdk/sources/utils/record_helper.py +8 -8
- airbyte_cdk/sql/shared/sql_processor.py +8 -9
- airbyte_cdk/test/entrypoint_wrapper.py +0 -4
- airbyte_cdk/test/mock_http/response_builder.py +8 -0
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/METADATA +2 -2
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/RECORD +35 -46
- airbyte_cdk/models/file_transfer_record_message.py +0 -13
- airbyte_cdk/test/declarative/__init__.py +0 -6
- airbyte_cdk/test/declarative/models/__init__.py +0 -7
- airbyte_cdk/test/declarative/models/scenario.py +0 -74
- airbyte_cdk/test/declarative/test_suites/__init__.py +0 -24
- airbyte_cdk/test/declarative/test_suites/connector_base.py +0 -202
- airbyte_cdk/test/declarative/test_suites/declarative_sources.py +0 -48
- airbyte_cdk/test/declarative/test_suites/destination_base.py +0 -12
- airbyte_cdk/test/declarative/test_suites/source_base.py +0 -129
- airbyte_cdk/test/declarative/utils/__init__.py +0 -0
- airbyte_cdk/test/declarative/utils/job_runner.py +0 -128
- airbyte_cdk/test/fixtures/__init__.py +0 -0
- airbyte_cdk/test/fixtures/auto.py +0 -14
- airbyte_cdk/test/pytest_config/plugin.py +0 -46
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/LICENSE_SHORT +0 -0
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/WHEEL +0 -0
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post42.dev14452300548.dist-info}/entry_points.txt +0 -0
@@ -1,129 +0,0 @@
|
|
1
|
-
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
2
|
-
"""Base class for source test suites."""
|
3
|
-
|
4
|
-
from dataclasses import asdict
|
5
|
-
from pathlib import Path
|
6
|
-
|
7
|
-
import pytest
|
8
|
-
|
9
|
-
from airbyte_cdk.models import (
|
10
|
-
AirbyteMessage,
|
11
|
-
AirbyteStream,
|
12
|
-
ConfiguredAirbyteCatalog,
|
13
|
-
ConfiguredAirbyteStream,
|
14
|
-
DestinationSyncMode,
|
15
|
-
SyncMode,
|
16
|
-
Type,
|
17
|
-
)
|
18
|
-
from airbyte_cdk.test import entrypoint_wrapper
|
19
|
-
from airbyte_cdk.test.declarative.models import (
|
20
|
-
ConnectorTestScenario,
|
21
|
-
)
|
22
|
-
from airbyte_cdk.test.declarative.test_suites.connector_base import (
|
23
|
-
ConnectorTestSuiteBase,
|
24
|
-
)
|
25
|
-
from airbyte_cdk.test.declarative.utils.job_runner import run_test_job
|
26
|
-
|
27
|
-
|
28
|
-
class SourceTestSuiteBase(ConnectorTestSuiteBase):
|
29
|
-
"""Base class for source test suites.
|
30
|
-
|
31
|
-
This class provides a base set of functionality for testing source connectors, and it
|
32
|
-
inherits all generic connector tests from the `ConnectorTestSuiteBase` class.
|
33
|
-
"""
|
34
|
-
|
35
|
-
def test_check(
|
36
|
-
self,
|
37
|
-
instance: ConnectorTestScenario,
|
38
|
-
) -> None:
|
39
|
-
"""Run `connection` acceptance tests."""
|
40
|
-
result: entrypoint_wrapper.EntrypointOutput = run_test_job(
|
41
|
-
self.create_connector(instance),
|
42
|
-
"check",
|
43
|
-
test_instance=instance,
|
44
|
-
)
|
45
|
-
conn_status_messages: list[AirbyteMessage] = [
|
46
|
-
msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS
|
47
|
-
] # 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)
|
50
|
-
)
|
51
|
-
|
52
|
-
def test_basic_read(
|
53
|
-
self,
|
54
|
-
instance: ConnectorTestScenario,
|
55
|
-
) -> None:
|
56
|
-
"""Run acceptance tests."""
|
57
|
-
discover_result = run_test_job(
|
58
|
-
self.create_connector(instance),
|
59
|
-
"discover",
|
60
|
-
test_instance=instance,
|
61
|
-
)
|
62
|
-
if instance.expect_exception:
|
63
|
-
assert discover_result.errors, "Expected exception but got none."
|
64
|
-
return
|
65
|
-
|
66
|
-
configured_catalog = ConfiguredAirbyteCatalog(
|
67
|
-
streams=[
|
68
|
-
ConfiguredAirbyteStream(
|
69
|
-
stream=stream,
|
70
|
-
sync_mode=SyncMode.full_refresh,
|
71
|
-
destination_sync_mode=DestinationSyncMode.append_dedup,
|
72
|
-
)
|
73
|
-
for stream in discover_result.catalog.catalog.streams
|
74
|
-
]
|
75
|
-
)
|
76
|
-
result = run_test_job(
|
77
|
-
self.create_connector(instance),
|
78
|
-
"read",
|
79
|
-
test_instance=instance,
|
80
|
-
catalog=configured_catalog,
|
81
|
-
)
|
82
|
-
|
83
|
-
if not result.records:
|
84
|
-
raise AssertionError("Expected records but got none.") # noqa: TRY003
|
85
|
-
|
86
|
-
def test_fail_with_bad_catalog(
|
87
|
-
self,
|
88
|
-
instance: ConnectorTestScenario,
|
89
|
-
) -> None:
|
90
|
-
"""Test that a bad catalog fails."""
|
91
|
-
invalid_configured_catalog = ConfiguredAirbyteCatalog(
|
92
|
-
streams=[
|
93
|
-
# Create ConfiguredAirbyteStream which is deliberately invalid
|
94
|
-
# with regard to the Airbyte Protocol.
|
95
|
-
# This should cause the connector to fail.
|
96
|
-
ConfiguredAirbyteStream(
|
97
|
-
stream=AirbyteStream(
|
98
|
-
name="__AIRBYTE__stream_that_does_not_exist",
|
99
|
-
json_schema={
|
100
|
-
"type": "object",
|
101
|
-
"properties": {"f1": {"type": "string"}},
|
102
|
-
},
|
103
|
-
supported_sync_modes=[SyncMode.full_refresh],
|
104
|
-
),
|
105
|
-
sync_mode="INVALID",
|
106
|
-
destination_sync_mode="INVALID",
|
107
|
-
)
|
108
|
-
]
|
109
|
-
)
|
110
|
-
# Set expected status to "failed" to ensure the test fails if the connector.
|
111
|
-
instance.status = "failed"
|
112
|
-
result = self.run_test_scenario(
|
113
|
-
"read",
|
114
|
-
test_scenario=instance,
|
115
|
-
catalog=asdict(invalid_configured_catalog),
|
116
|
-
)
|
117
|
-
assert result.errors, "Expected errors but got none."
|
118
|
-
assert result.trace_messages, "Expected trace messages but got none."
|
119
|
-
|
120
|
-
def test_discover(
|
121
|
-
self,
|
122
|
-
instance: ConnectorTestScenario,
|
123
|
-
) -> None:
|
124
|
-
"""Run acceptance tests."""
|
125
|
-
run_test_job(
|
126
|
-
self.create_connector(instance),
|
127
|
-
"check",
|
128
|
-
test_instance=instance,
|
129
|
-
)
|
File without changes
|
@@ -1,128 +0,0 @@
|
|
1
|
-
import tempfile
|
2
|
-
import uuid
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import Any, Callable, Literal
|
5
|
-
|
6
|
-
import orjson
|
7
|
-
|
8
|
-
from airbyte_cdk import Connector
|
9
|
-
from airbyte_cdk.models import (
|
10
|
-
Status,
|
11
|
-
)
|
12
|
-
from airbyte_cdk.sources.abstract_source import AbstractSource
|
13
|
-
from airbyte_cdk.sources.declarative.declarative_source import DeclarativeSource
|
14
|
-
from airbyte_cdk.test import entrypoint_wrapper
|
15
|
-
from airbyte_cdk.test.declarative.models import (
|
16
|
-
ConnectorTestScenario,
|
17
|
-
)
|
18
|
-
|
19
|
-
|
20
|
-
def run_test_job(
|
21
|
-
connector: Connector | type[Connector] | Callable[[], Connector],
|
22
|
-
verb: Literal["read", "check", "discover"],
|
23
|
-
test_instance: ConnectorTestScenario,
|
24
|
-
*,
|
25
|
-
catalog: dict[str, Any] | None = None,
|
26
|
-
) -> entrypoint_wrapper.EntrypointOutput:
|
27
|
-
"""Run a test job from provided CLI args and return the result."""
|
28
|
-
if not connector:
|
29
|
-
raise ValueError("Connector is required")
|
30
|
-
|
31
|
-
connector_obj: Connector
|
32
|
-
if isinstance(connector, type):
|
33
|
-
connector_obj = connector()
|
34
|
-
elif isinstance(connector, Connector):
|
35
|
-
connector_obj = connector
|
36
|
-
elif isinstance(connector, DeclarativeSource | AbstractSource):
|
37
|
-
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
|
-
)
|
49
|
-
else:
|
50
|
-
raise ValueError(f"Invalid source type: {type(connector)}")
|
51
|
-
|
52
|
-
args: list[str] = [verb]
|
53
|
-
if test_instance.config_path:
|
54
|
-
args += ["--config", str(test_instance.config_path)]
|
55
|
-
elif test_instance.config_dict:
|
56
|
-
config_path = (
|
57
|
-
Path(tempfile.gettempdir()) / "airbyte-test" / f"temp_config_{uuid.uuid4().hex}.json"
|
58
|
-
)
|
59
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
60
|
-
config_path.write_text(orjson.dumps(test_instance.config_dict).decode())
|
61
|
-
args += ["--config", str(config_path)]
|
62
|
-
|
63
|
-
catalog_path: Path | None = None
|
64
|
-
if verb not in ["discover", "check"]:
|
65
|
-
# We need a catalog for read.
|
66
|
-
if catalog:
|
67
|
-
# Write the catalog to a temp json file and pass the path to the file as an argument.
|
68
|
-
catalog_path = (
|
69
|
-
Path(tempfile.gettempdir())
|
70
|
-
/ "airbyte-test"
|
71
|
-
/ f"temp_catalog_{uuid.uuid4().hex}.json"
|
72
|
-
)
|
73
|
-
catalog_path.parent.mkdir(parents=True, exist_ok=True)
|
74
|
-
catalog_path.write_text(orjson.dumps(catalog).decode())
|
75
|
-
elif test_instance.configured_catalog_path:
|
76
|
-
catalog_path = Path(test_instance.configured_catalog_path)
|
77
|
-
|
78
|
-
if catalog_path:
|
79
|
-
args += ["--catalog", str(catalog_path)]
|
80
|
-
|
81
|
-
# This is a bit of a hack because the source needs the catalog early.
|
82
|
-
# Because it *also* can fail, we have ot redundantly wrap it in a try/except block.
|
83
|
-
|
84
|
-
result: entrypoint_wrapper.EntrypointOutput = entrypoint_wrapper._run_command( # noqa: SLF001 # Non-public API
|
85
|
-
source=connector_obj,
|
86
|
-
args=args,
|
87
|
-
expecting_exception=test_instance.expect_exception,
|
88
|
-
)
|
89
|
-
if result.errors and not test_instance.expect_exception:
|
90
|
-
raise AssertionError(
|
91
|
-
"\n\n".join(
|
92
|
-
[str(err.trace.error).replace("\\n", "\n") for err in result.errors],
|
93
|
-
)
|
94
|
-
)
|
95
|
-
|
96
|
-
if verb == "check":
|
97
|
-
# Check is expected to fail gracefully without an exception.
|
98
|
-
# Instead, we assert that we have a CONNECTION_STATUS message with
|
99
|
-
# 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 len(result.connection_status_messages) == 1, (
|
104
|
-
"Expected exactly one CONNECTION_STATUS message. Got "
|
105
|
-
f"{len(result.connection_status_messages)}:\n"
|
106
|
-
+ "\n".join([str(msg) for msg in result.connection_status_messages])
|
107
|
-
)
|
108
|
-
if test_instance.expect_exception:
|
109
|
-
assert result.connection_status_messages[0].connectionStatus.status == Status.FAILED, (
|
110
|
-
"Expected CONNECTION_STATUS message to be FAILED. Got: \n"
|
111
|
-
+ "\n".join([str(msg) for msg in result.connection_status_messages])
|
112
|
-
)
|
113
|
-
return result
|
114
|
-
|
115
|
-
# For all other verbs, we assert check that an exception is raised (or not).
|
116
|
-
if test_instance.expect_exception:
|
117
|
-
if not result.errors:
|
118
|
-
raise AssertionError("Expected exception but got none.")
|
119
|
-
|
120
|
-
return result
|
121
|
-
if result.errors:
|
122
|
-
raise AssertionError(
|
123
|
-
"\n\n".join(
|
124
|
-
[str(err.trace.error).replace("\\n", "\n") for err in result.errors],
|
125
|
-
)
|
126
|
-
)
|
127
|
-
|
128
|
-
return result
|
File without changes
|
@@ -1,14 +0,0 @@
|
|
1
|
-
"""Auto-use fixtures for pytest.
|
2
|
-
|
3
|
-
WARNING: Importing this module will automatically apply these fixtures. If you want to selectively
|
4
|
-
enable fixtures in a different context, you can import directly from the `fixtures.general` module.
|
5
|
-
|
6
|
-
|
7
|
-
Usage:
|
8
|
-
|
9
|
-
```python
|
10
|
-
from airbyte_cdk.test.fixtures import auto
|
11
|
-
# OR
|
12
|
-
from airbyte_cdk.test.fixtures.auto import *
|
13
|
-
```
|
14
|
-
"""
|
@@ -1,46 +0,0 @@
|
|
1
|
-
"""Global pytest configuration for the Airbyte CDK tests."""
|
2
|
-
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import Optional
|
5
|
-
|
6
|
-
import pytest
|
7
|
-
|
8
|
-
|
9
|
-
def pytest_collect_file(parent: Optional[pytest.Module], path: Path) -> pytest.Module | None:
|
10
|
-
"""Collect test files based on their names."""
|
11
|
-
if path.name == "test_connector.py":
|
12
|
-
return cast(pytest.Module, pytest.Module.from_parent(parent, path=path))
|
13
|
-
|
14
|
-
return None
|
15
|
-
|
16
|
-
|
17
|
-
def pytest_configure(config: pytest.Config) -> None:
|
18
|
-
config.addinivalue_line("markers", "connector: mark test as a connector test")
|
19
|
-
|
20
|
-
|
21
|
-
def pytest_addoption(parser: pytest.Parser) -> None:
|
22
|
-
parser.addoption(
|
23
|
-
"--run-connector",
|
24
|
-
action="store_true",
|
25
|
-
default=False,
|
26
|
-
help="run connector tests",
|
27
|
-
)
|
28
|
-
|
29
|
-
|
30
|
-
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
31
|
-
if config.getoption("--run-connector"):
|
32
|
-
return
|
33
|
-
skip_connector = pytest.mark.skip(reason="need --run-connector option to run")
|
34
|
-
for item in items:
|
35
|
-
if "connector" in item.keywords:
|
36
|
-
item.add_marker(skip_connector)
|
37
|
-
|
38
|
-
|
39
|
-
def pytest_runtest_setup(item: pytest.Item) -> None:
|
40
|
-
# This hook is called before each test function is executed
|
41
|
-
print(f"Setting up test: {item.name}")
|
42
|
-
|
43
|
-
|
44
|
-
def pytest_runtest_teardown(item: pytest.Item, nextitem: Optional[pytest.Item]) -> None:
|
45
|
-
# This hook is called after each test function is executed
|
46
|
-
print(f"Tearing down test: {item.name}")
|
File without changes
|
File without changes
|
File without changes
|