airbyte-cdk 6.45.0.post24.dev14387467928__py3-none-any.whl → 6.45.1.post44.dev14417290834__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/connector_builder_handler.py +12 -3
- airbyte_cdk/connector_builder/main.py +3 -3
- airbyte_cdk/connector_builder/test_reader/reader.py +2 -0
- airbyte_cdk/sources/concurrent_source/concurrent_source.py +3 -3
- airbyte_cdk/sources/declarative/interpolation/filters.py +49 -2
- airbyte_cdk/sources/file_based/file_based_source.py +3 -3
- airbyte_cdk/sources/file_based/file_types/avro_parser.py +1 -1
- airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +3 -3
- airbyte_cdk/sql/shared/sql_processor.py +8 -9
- airbyte_cdk/test/declarative/test_suites/__init__.py +1 -0
- airbyte_cdk/test/declarative/test_suites/connector_base.py +49 -40
- airbyte_cdk/test/declarative/test_suites/declarative_sources.py +43 -17
- airbyte_cdk/test/declarative/test_suites/source_base.py +7 -8
- airbyte_cdk/test/declarative/utils/job_runner.py +48 -26
- airbyte_cdk/test/pytest_config/plugin.py +3 -3
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post44.dev14417290834.dist-info}/METADATA +2 -1
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post44.dev14417290834.dist-info}/RECORD +21 -21
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post44.dev14417290834.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post44.dev14417290834.dist-info}/LICENSE_SHORT +0 -0
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post44.dev14417290834.dist-info}/WHEEL +0 -0
- {airbyte_cdk-6.45.0.post24.dev14387467928.dist-info → airbyte_cdk-6.45.1.post44.dev14417290834.dist-info}/entry_points.txt +0 -0
@@ -35,8 +35,10 @@ MAX_RECORDS_KEY = "max_records"
|
|
35
35
|
MAX_STREAMS_KEY = "max_streams"
|
36
36
|
|
37
37
|
|
38
|
-
@dataclass
|
38
|
+
@dataclass(kw_only=True)
|
39
39
|
class TestLimits:
|
40
|
+
__test__: bool = False # Prevent pytest from treating this as a test case, despite its name
|
41
|
+
|
40
42
|
max_records: int = field(default=DEFAULT_MAXIMUM_RECORDS)
|
41
43
|
max_pages_per_slice: int = field(default=DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE)
|
42
44
|
max_slices: int = field(default=DEFAULT_MAXIMUM_NUMBER_OF_SLICES)
|
@@ -51,7 +53,12 @@ def get_limits(config: Mapping[str, Any]) -> TestLimits:
|
|
51
53
|
max_slices = command_config.get(MAX_SLICES_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_SLICES
|
52
54
|
max_records = command_config.get(MAX_RECORDS_KEY) or DEFAULT_MAXIMUM_RECORDS
|
53
55
|
max_streams = command_config.get(MAX_STREAMS_KEY) or DEFAULT_MAXIMUM_STREAMS
|
54
|
-
return TestLimits(
|
56
|
+
return TestLimits(
|
57
|
+
max_records=max_records,
|
58
|
+
max_pages_per_slice=max_pages_per_slice,
|
59
|
+
max_slices=max_slices,
|
60
|
+
max_streams=max_streams,
|
61
|
+
)
|
55
62
|
|
56
63
|
|
57
64
|
def create_source(config: Mapping[str, Any], limits: TestLimits) -> ManifestDeclarativeSource:
|
@@ -79,7 +86,9 @@ def read_stream(
|
|
79
86
|
) -> AirbyteMessage:
|
80
87
|
try:
|
81
88
|
test_read_handler = TestReader(
|
82
|
-
limits.max_pages_per_slice,
|
89
|
+
max_pages_per_slice=limits.max_pages_per_slice,
|
90
|
+
max_slices=limits.max_slices,
|
91
|
+
max_record_limit=limits.max_records,
|
83
92
|
)
|
84
93
|
# The connector builder only supports a single stream
|
85
94
|
stream_name = configured_catalog.streams[0].stream.name
|
@@ -78,9 +78,9 @@ def handle_connector_builder_request(
|
|
78
78
|
if command == "resolve_manifest":
|
79
79
|
return resolve_manifest(source)
|
80
80
|
elif command == "test_read":
|
81
|
-
assert (
|
82
|
-
|
83
|
-
)
|
81
|
+
assert catalog is not None, (
|
82
|
+
"`test_read` requires a valid `ConfiguredAirbyteCatalog`, got None."
|
83
|
+
)
|
84
84
|
return read_stream(source, config, catalog, state, limits)
|
85
85
|
elif command == "full_resolve_manifest":
|
86
86
|
return full_resolve_manifest(source, limits)
|
@@ -49,9 +49,9 @@ class ConcurrentSource:
|
|
49
49
|
too_many_generator = (
|
50
50
|
not is_single_threaded and initial_number_of_partitions_to_generate >= num_workers
|
51
51
|
)
|
52
|
-
assert (
|
53
|
-
|
54
|
-
)
|
52
|
+
assert not too_many_generator, (
|
53
|
+
"It is required to have more workers than threads generating partitions"
|
54
|
+
)
|
55
55
|
threadpool = ThreadPoolManager(
|
56
56
|
concurrent.futures.ThreadPoolExecutor(
|
57
57
|
max_workers=num_workers, thread_name_prefix="workerpool"
|
@@ -4,9 +4,10 @@
|
|
4
4
|
|
5
5
|
import base64
|
6
6
|
import hashlib
|
7
|
+
import hmac as hmac_lib
|
7
8
|
import json
|
8
9
|
import re
|
9
|
-
from typing import Any, Optional
|
10
|
+
from typing import Any, Dict, Optional
|
10
11
|
|
11
12
|
|
12
13
|
def hash(value: Any, hash_type: str = "md5", salt: Optional[str] = None) -> str:
|
@@ -135,5 +136,51 @@ def regex_search(value: str, regex: str) -> str:
|
|
135
136
|
return ""
|
136
137
|
|
137
138
|
|
138
|
-
|
139
|
+
def hmac(value: Any, key: str, hash_type: str = "sha256") -> str:
|
140
|
+
"""
|
141
|
+
Implementation of a custom Jinja2 hmac filter with SHA-256 support.
|
142
|
+
|
143
|
+
This filter creates a Hash-based Message Authentication Code (HMAC) using a cryptographic
|
144
|
+
hash function and a secret key. Currently only supports SHA-256, and returns hexdigest of the signature.
|
145
|
+
|
146
|
+
Example usage in a low code connector:
|
147
|
+
|
148
|
+
auth_headers:
|
149
|
+
$ref: "#/definitions/base_auth"
|
150
|
+
$parameters:
|
151
|
+
signature: "{{ 'message_to_sign' | hmac('my_secret_key') }}"
|
152
|
+
|
153
|
+
:param value: The message to be authenticated
|
154
|
+
:param key: The secret key for the HMAC
|
155
|
+
:param hash_type: Hash algorithm to use (default: sha256)
|
156
|
+
:return: HMAC digest as a hexadecimal string
|
157
|
+
"""
|
158
|
+
# Define allowed hash functions
|
159
|
+
ALLOWED_HASH_TYPES: Dict[str, Any] = {
|
160
|
+
"sha256": hashlib.sha256,
|
161
|
+
}
|
162
|
+
|
163
|
+
if hash_type not in ALLOWED_HASH_TYPES:
|
164
|
+
raise ValueError(
|
165
|
+
f"Hash type '{hash_type}' is not allowed. Allowed types: {', '.join(ALLOWED_HASH_TYPES.keys())}"
|
166
|
+
)
|
167
|
+
|
168
|
+
hmac_obj = hmac_lib.new(
|
169
|
+
key=str(key).encode("utf-8"),
|
170
|
+
msg=str(value).encode("utf-8"),
|
171
|
+
digestmod=ALLOWED_HASH_TYPES[hash_type],
|
172
|
+
)
|
173
|
+
|
174
|
+
return hmac_obj.hexdigest()
|
175
|
+
|
176
|
+
|
177
|
+
_filters_list = [
|
178
|
+
hash,
|
179
|
+
base64encode,
|
180
|
+
base64decode,
|
181
|
+
base64binascii_decode,
|
182
|
+
string,
|
183
|
+
regex_search,
|
184
|
+
hmac,
|
185
|
+
]
|
139
186
|
filters = {f.__name__: f for f in _filters_list}
|
@@ -282,9 +282,9 @@ class FileBasedSource(ConcurrentSourceAdapter, ABC):
|
|
282
282
|
and hasattr(self, "_concurrency_level")
|
283
283
|
and self._concurrency_level is not None
|
284
284
|
):
|
285
|
-
assert (
|
286
|
-
|
287
|
-
)
|
285
|
+
assert state_manager is not None, (
|
286
|
+
"No ConnectorStateManager was created, but it is required for incremental syncs. This is unexpected. Please contact Support."
|
287
|
+
)
|
288
288
|
|
289
289
|
cursor = self.cursor_cls(
|
290
290
|
stream_config,
|
@@ -154,7 +154,7 @@ class AvroParser(FileTypeParser):
|
|
154
154
|
# For example: ^-?\d{1,5}(?:\.\d{1,3})?$ would accept 12345.123 and 123456.12345 would be rejected
|
155
155
|
return {
|
156
156
|
"type": "string",
|
157
|
-
"pattern": f"^-?\\d{{{1,max_whole_number_range}}}(?:\\.\\d{1,decimal_range})?$",
|
157
|
+
"pattern": f"^-?\\d{{{1, max_whole_number_range}}}(?:\\.\\d{1, decimal_range})?$",
|
158
158
|
}
|
159
159
|
elif "logicalType" in avro_field:
|
160
160
|
if avro_field["logicalType"] not in AVRO_LOGICAL_TYPE_TO_JSON:
|
@@ -284,9 +284,9 @@ class FileBasedStreamPartition(Partition):
|
|
284
284
|
def to_slice(self) -> Optional[Mapping[str, Any]]:
|
285
285
|
if self._slice is None:
|
286
286
|
return None
|
287
|
-
assert (
|
288
|
-
len(self._slice[
|
289
|
-
)
|
287
|
+
assert len(self._slice["files"]) == 1, (
|
288
|
+
f"Expected 1 file per partition but got {len(self._slice['files'])} for stream {self.stream_name()}"
|
289
|
+
)
|
290
290
|
file = self._slice["files"][0]
|
291
291
|
return {"files": [file]}
|
292
292
|
|
@@ -326,9 +326,9 @@ class SqlProcessorBase(abc.ABC):
|
|
326
326
|
|
327
327
|
if DEBUG_MODE:
|
328
328
|
found_schemas = schemas_list
|
329
|
-
assert (
|
330
|
-
schema_name
|
331
|
-
)
|
329
|
+
assert schema_name in found_schemas, (
|
330
|
+
f"Schema {schema_name} was not created. Found: {found_schemas}"
|
331
|
+
)
|
332
332
|
|
333
333
|
def _quote_identifier(self, identifier: str) -> str:
|
334
334
|
"""Return the given identifier, quoted."""
|
@@ -617,10 +617,10 @@ class SqlProcessorBase(abc.ABC):
|
|
617
617
|
self._execute_sql(
|
618
618
|
f"""
|
619
619
|
INSERT INTO {self._fully_qualified(final_table_name)} (
|
620
|
-
{f
|
620
|
+
{f",{nl} ".join(columns)}
|
621
621
|
)
|
622
622
|
SELECT
|
623
|
-
{f
|
623
|
+
{f",{nl} ".join(columns)}
|
624
624
|
FROM {self._fully_qualified(temp_table_name)}
|
625
625
|
""",
|
626
626
|
)
|
@@ -645,8 +645,7 @@ class SqlProcessorBase(abc.ABC):
|
|
645
645
|
deletion_name = f"{final_table_name}_deleteme"
|
646
646
|
commands = "\n".join(
|
647
647
|
[
|
648
|
-
f"ALTER TABLE {self._fully_qualified(final_table_name)} RENAME "
|
649
|
-
f"TO {deletion_name};",
|
648
|
+
f"ALTER TABLE {self._fully_qualified(final_table_name)} RENAME TO {deletion_name};",
|
650
649
|
f"ALTER TABLE {self._fully_qualified(temp_table_name)} RENAME "
|
651
650
|
f"TO {final_table_name};",
|
652
651
|
f"DROP TABLE {self._fully_qualified(deletion_name)};",
|
@@ -686,10 +685,10 @@ class SqlProcessorBase(abc.ABC):
|
|
686
685
|
{set_clause}
|
687
686
|
WHEN NOT MATCHED THEN INSERT
|
688
687
|
(
|
689
|
-
{f
|
688
|
+
{f",{nl} ".join(columns)}
|
690
689
|
)
|
691
690
|
VALUES (
|
692
|
-
tmp.{f
|
691
|
+
tmp.{f",{nl} tmp.".join(columns)}
|
693
692
|
);
|
694
693
|
""",
|
695
694
|
)
|
@@ -6,6 +6,7 @@ Here we have base classes for a robust set of declarative connector test suites.
|
|
6
6
|
|
7
7
|
from airbyte_cdk.test.declarative.test_suites.connector_base import (
|
8
8
|
ConnectorTestScenario,
|
9
|
+
ConnectorTestSuiteBase,
|
9
10
|
generate_tests,
|
10
11
|
)
|
11
12
|
from airbyte_cdk.test.declarative.test_suites.declarative_sources import (
|
@@ -4,34 +4,28 @@
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
6
|
import abc
|
7
|
-
import functools
|
8
7
|
import inspect
|
9
8
|
import sys
|
10
9
|
from pathlib import Path
|
11
|
-
from typing import Any,
|
10
|
+
from typing import Any, Literal
|
12
11
|
|
13
12
|
import pytest
|
14
13
|
import yaml
|
15
|
-
from
|
16
|
-
from typing_extensions import override
|
14
|
+
from boltons.typeutils import classproperty
|
17
15
|
|
18
16
|
from airbyte_cdk import Connector
|
19
17
|
from airbyte_cdk.models import (
|
20
18
|
AirbyteMessage,
|
21
19
|
Type,
|
22
20
|
)
|
23
|
-
from airbyte_cdk.sources import Source
|
24
|
-
from airbyte_cdk.sources.declarative.declarative_source import (
|
25
|
-
AbstractSource,
|
26
|
-
DeclarativeSource,
|
27
|
-
)
|
28
21
|
from airbyte_cdk.test import entrypoint_wrapper
|
29
22
|
from airbyte_cdk.test.declarative.models import (
|
30
23
|
ConnectorTestScenario,
|
31
24
|
)
|
32
|
-
from airbyte_cdk.test.declarative.utils.job_runner import run_test_job
|
25
|
+
from airbyte_cdk.test.declarative.utils.job_runner import IConnector, run_test_job
|
33
26
|
|
34
27
|
ACCEPTANCE_TEST_CONFIG = "acceptance-test-config.yml"
|
28
|
+
MANIFEST_YAML = "manifest.yaml"
|
35
29
|
|
36
30
|
|
37
31
|
class JavaClass(str):
|
@@ -49,7 +43,7 @@ class RunnableConnector(abc.ABC):
|
|
49
43
|
def launch(cls, args: list[str] | None) -> None: ...
|
50
44
|
|
51
45
|
|
52
|
-
def generate_tests(metafunc) -> None:
|
46
|
+
def generate_tests(metafunc: pytest.Metafunc) -> None:
|
53
47
|
"""
|
54
48
|
A helper for pytest_generate_tests hook.
|
55
49
|
|
@@ -96,23 +90,21 @@ def generate_tests(metafunc) -> None:
|
|
96
90
|
class ConnectorTestSuiteBase(abc.ABC):
|
97
91
|
"""Base class for connector test suites."""
|
98
92
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
93
|
+
@classmethod
|
94
|
+
def get_test_class_dir(cls) -> Path:
|
95
|
+
"""Get the file path that contains the class."""
|
96
|
+
module = sys.modules[cls.__module__]
|
97
|
+
# Get the directory containing the test file
|
98
|
+
return Path(inspect.getfile(module)).parent
|
105
99
|
|
106
100
|
connector: type[Connector] | Path | JavaClass | DockerImage | None = None
|
107
101
|
"""The connector class or path to the connector to test."""
|
108
102
|
|
109
|
-
working_dir: Path | None = None
|
110
|
-
"""The root directory of the connector source code."""
|
111
|
-
|
112
103
|
@classmethod
|
113
104
|
def create_connector(
|
114
|
-
cls,
|
115
|
-
|
105
|
+
cls,
|
106
|
+
scenario: ConnectorTestScenario,
|
107
|
+
) -> IConnector:
|
116
108
|
"""Instantiate the connector class."""
|
117
109
|
raise NotImplementedError("Subclasses must implement this method.")
|
118
110
|
|
@@ -121,7 +113,7 @@ class ConnectorTestSuiteBase(abc.ABC):
|
|
121
113
|
verb: Literal["read", "check", "discover"],
|
122
114
|
test_scenario: ConnectorTestScenario,
|
123
115
|
*,
|
124
|
-
catalog: dict | None = None,
|
116
|
+
catalog: dict[str, Any] | None = None,
|
125
117
|
) -> entrypoint_wrapper.EntrypointOutput:
|
126
118
|
"""Run a test job from provided CLI args and return the result."""
|
127
119
|
return run_test_job(
|
@@ -146,27 +138,43 @@ class ConnectorTestSuiteBase(abc.ABC):
|
|
146
138
|
msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS
|
147
139
|
] # noqa: SLF001 # Non-public API
|
148
140
|
assert len(conn_status_messages) == 1, (
|
149
|
-
"Expected exactly one CONNECTION_STATUS message. Got:
|
141
|
+
f"Expected exactly one CONNECTION_STATUS message. Got: {result._messages}"
|
150
142
|
)
|
151
143
|
|
152
144
|
@classmethod
|
153
|
-
|
154
|
-
|
155
|
-
|
145
|
+
def get_connector_root_dir(cls) -> Path:
|
146
|
+
"""Get the root directory of the connector."""
|
147
|
+
for parent in cls.get_test_class_dir().parents:
|
148
|
+
if (parent / MANIFEST_YAML).exists():
|
149
|
+
return parent
|
150
|
+
if (parent / ACCEPTANCE_TEST_CONFIG).exists():
|
151
|
+
return parent
|
152
|
+
if parent.name == "airbyte_cdk":
|
153
|
+
break
|
154
|
+
# If we reach here, we didn't find the manifest file in any parent directory
|
155
|
+
# Check if the manifest file exists in the current directory
|
156
|
+
for parent in Path.cwd().parents:
|
157
|
+
if (parent / MANIFEST_YAML).exists():
|
158
|
+
return parent
|
159
|
+
if (parent / ACCEPTANCE_TEST_CONFIG).exists():
|
160
|
+
return parent
|
161
|
+
if parent.name == "airbyte_cdk":
|
162
|
+
break
|
156
163
|
|
157
|
-
Check vwd and parent directories of cwd for the config file, and return the first one found.
|
158
|
-
|
159
|
-
Give up if the config file is not found in any parent directory.
|
160
|
-
"""
|
161
|
-
current_dir = Path.cwd()
|
162
|
-
for parent_dir in current_dir.parents:
|
163
|
-
config_path = parent_dir / ACCEPTANCE_TEST_CONFIG
|
164
|
-
if config_path.exists():
|
165
|
-
return config_path
|
166
164
|
raise FileNotFoundError(
|
167
|
-
|
165
|
+
"Could not find connector root directory relative to "
|
166
|
+
f"'{str(cls.get_test_class_dir())}' or '{str(Path.cwd())}'."
|
168
167
|
)
|
169
168
|
|
169
|
+
@classproperty
|
170
|
+
def acceptance_test_config_path(cls) -> Path:
|
171
|
+
"""Get the path to the acceptance test config file."""
|
172
|
+
result = cls.get_connector_root_dir() / ACCEPTANCE_TEST_CONFIG
|
173
|
+
if result.exists():
|
174
|
+
return result
|
175
|
+
|
176
|
+
raise FileNotFoundError(f"Acceptance test config file not found at: {str(result)}")
|
177
|
+
|
170
178
|
@classmethod
|
171
179
|
def get_scenarios(
|
172
180
|
cls,
|
@@ -193,10 +201,11 @@ class ConnectorTestSuiteBase(abc.ABC):
|
|
193
201
|
for test in all_tests_config["acceptance_tests"][category]["tests"]
|
194
202
|
if "iam_role" not in test["config_path"]
|
195
203
|
]
|
196
|
-
|
204
|
+
connector_root = cls.get_connector_root_dir().absolute()
|
197
205
|
for test in tests_scenarios:
|
198
206
|
if test.config_path:
|
199
|
-
test.config_path =
|
207
|
+
test.config_path = connector_root / test.config_path
|
200
208
|
if test.configured_catalog_path:
|
201
|
-
test.configured_catalog_path =
|
209
|
+
test.configured_catalog_path = connector_root / test.configured_catalog_path
|
210
|
+
|
202
211
|
return tests_scenarios
|
@@ -4,14 +4,17 @@ from pathlib import Path
|
|
4
4
|
from typing import Any, cast
|
5
5
|
|
6
6
|
import yaml
|
7
|
+
from boltons.typeutils import classproperty
|
7
8
|
|
8
9
|
from airbyte_cdk.sources.declarative.concurrent_declarative_source import (
|
9
10
|
ConcurrentDeclarativeSource,
|
10
11
|
)
|
11
12
|
from airbyte_cdk.test.declarative.models import ConnectorTestScenario
|
13
|
+
from airbyte_cdk.test.declarative.test_suites.connector_base import MANIFEST_YAML
|
12
14
|
from airbyte_cdk.test.declarative.test_suites.source_base import (
|
13
15
|
SourceTestSuiteBase,
|
14
16
|
)
|
17
|
+
from airbyte_cdk.test.declarative.utils.job_runner import IConnector
|
15
18
|
|
16
19
|
|
17
20
|
def md5_checksum(file_path: Path) -> str:
|
@@ -20,29 +23,52 @@ def md5_checksum(file_path: Path) -> str:
|
|
20
23
|
|
21
24
|
|
22
25
|
class DeclarativeSourceTestSuite(SourceTestSuiteBase):
|
23
|
-
|
24
|
-
|
26
|
+
@classproperty
|
27
|
+
def manifest_yaml_path(cls) -> Path:
|
28
|
+
"""Get the path to the manifest.yaml file."""
|
29
|
+
result = cls.get_connector_root_dir() / MANIFEST_YAML
|
30
|
+
if result.exists():
|
31
|
+
return result
|
25
32
|
|
33
|
+
raise FileNotFoundError(
|
34
|
+
f"Manifest YAML file not found at {result}. "
|
35
|
+
"Please ensure that the test suite is run in the correct directory.",
|
36
|
+
)
|
37
|
+
|
38
|
+
@classproperty
|
39
|
+
def components_py_path(cls) -> Path | None:
|
40
|
+
"""Get the path to the components.py file."""
|
41
|
+
result = cls.get_connector_root_dir() / "components.py"
|
42
|
+
if result.exists():
|
43
|
+
return result
|
44
|
+
|
45
|
+
return None
|
46
|
+
|
47
|
+
@classmethod
|
26
48
|
def create_connector(
|
27
|
-
|
28
|
-
|
49
|
+
cls,
|
50
|
+
scenario: ConnectorTestScenario,
|
51
|
+
) -> IConnector:
|
29
52
|
"""Create a connector instance for the test suite."""
|
30
|
-
config =
|
31
|
-
# catalog =
|
32
|
-
# state =
|
33
|
-
# source_config =
|
53
|
+
config: dict[str, Any] = scenario.get_config_dict()
|
54
|
+
# catalog = scenario.get_catalog()
|
55
|
+
# state = scenario.get_state()
|
56
|
+
# source_config = scenario.get_source_config()
|
34
57
|
|
35
|
-
manifest_dict = yaml.safe_load(
|
36
|
-
if
|
58
|
+
manifest_dict = yaml.safe_load(cls.manifest_yaml_path.read_text())
|
59
|
+
if cls.components_py_path and cls.components_py_path.exists():
|
37
60
|
os.environ["AIRBYTE_ENABLE_UNSAFE_CODE"] = "true"
|
38
|
-
config["__injected_components_py"] =
|
61
|
+
config["__injected_components_py"] = cls.components_py_path.read_text()
|
39
62
|
config["__injected_components_py_checksums"] = {
|
40
|
-
"md5": md5_checksum(
|
63
|
+
"md5": md5_checksum(cls.components_py_path),
|
41
64
|
}
|
42
65
|
|
43
|
-
return
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
66
|
+
return cast(
|
67
|
+
IConnector,
|
68
|
+
ConcurrentDeclarativeSource(
|
69
|
+
config=config,
|
70
|
+
catalog=None,
|
71
|
+
state=None,
|
72
|
+
source_config=manifest_dict,
|
73
|
+
),
|
48
74
|
)
|
@@ -2,9 +2,6 @@
|
|
2
2
|
"""Base class for source test suites."""
|
3
3
|
|
4
4
|
from dataclasses import asdict
|
5
|
-
from pathlib import Path
|
6
|
-
|
7
|
-
import pytest
|
8
5
|
|
9
6
|
from airbyte_cdk.models import (
|
10
7
|
AirbyteMessage,
|
@@ -45,8 +42,10 @@ class SourceTestSuiteBase(ConnectorTestSuiteBase):
|
|
45
42
|
conn_status_messages: list[AirbyteMessage] = [
|
46
43
|
msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS
|
47
44
|
] # noqa: SLF001 # Non-public API
|
48
|
-
|
49
|
-
|
45
|
+
num_status_messages = len(conn_status_messages)
|
46
|
+
assert num_status_messages == 1, (
|
47
|
+
f"Expected exactly one CONNECTION_STATUS message. Got {num_status_messages}: \n"
|
48
|
+
+ "\n".join([str(m) for m in result._messages])
|
50
49
|
)
|
51
50
|
|
52
51
|
def test_basic_read(
|
@@ -70,7 +69,7 @@ class SourceTestSuiteBase(ConnectorTestSuiteBase):
|
|
70
69
|
sync_mode=SyncMode.full_refresh,
|
71
70
|
destination_sync_mode=DestinationSyncMode.append_dedup,
|
72
71
|
)
|
73
|
-
for stream in discover_result.catalog.catalog.streams
|
72
|
+
for stream in discover_result.catalog.catalog.streams # type: ignore [reportOptionalMemberAccess, union-attr]
|
74
73
|
]
|
75
74
|
)
|
76
75
|
result = run_test_job(
|
@@ -102,8 +101,8 @@ class SourceTestSuiteBase(ConnectorTestSuiteBase):
|
|
102
101
|
},
|
103
102
|
supported_sync_modes=[SyncMode.full_refresh],
|
104
103
|
),
|
105
|
-
sync_mode="INVALID",
|
106
|
-
destination_sync_mode="INVALID",
|
104
|
+
sync_mode="INVALID", # type: ignore [reportArgumentType]
|
105
|
+
destination_sync_mode="INVALID", # type: ignore [reportArgumentType]
|
107
106
|
)
|
108
107
|
]
|
109
108
|
)
|
@@ -1,53 +1,62 @@
|
|
1
1
|
import tempfile
|
2
2
|
import uuid
|
3
|
+
from dataclasses import asdict
|
3
4
|
from pathlib import Path
|
4
5
|
from typing import Any, Callable, Literal
|
5
6
|
|
6
7
|
import orjson
|
8
|
+
from typing_extensions import Protocol, runtime_checkable
|
7
9
|
|
8
|
-
from airbyte_cdk import Connector
|
9
10
|
from airbyte_cdk.models import (
|
11
|
+
ConfiguredAirbyteCatalog,
|
10
12
|
Status,
|
11
13
|
)
|
12
|
-
from airbyte_cdk.sources.abstract_source import AbstractSource
|
13
|
-
from airbyte_cdk.sources.declarative.declarative_source import DeclarativeSource
|
14
14
|
from airbyte_cdk.test import entrypoint_wrapper
|
15
15
|
from airbyte_cdk.test.declarative.models import (
|
16
16
|
ConnectorTestScenario,
|
17
17
|
)
|
18
18
|
|
19
19
|
|
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
|
+
|
20
29
|
def run_test_job(
|
21
|
-
connector:
|
30
|
+
connector: IConnector | type[IConnector] | Callable[[], IConnector],
|
22
31
|
verb: Literal["read", "check", "discover"],
|
23
32
|
test_instance: ConnectorTestScenario,
|
24
33
|
*,
|
25
|
-
catalog: dict[str, Any] | None = None,
|
34
|
+
catalog: ConfiguredAirbyteCatalog | dict[str, Any] | None = None,
|
26
35
|
) -> entrypoint_wrapper.EntrypointOutput:
|
27
36
|
"""Run a test job from provided CLI args and return the result."""
|
28
37
|
if not connector:
|
29
38
|
raise ValueError("Connector is required")
|
30
39
|
|
31
|
-
|
32
|
-
|
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.
|
33
47
|
connector_obj = connector()
|
34
|
-
elif
|
35
|
-
|
36
|
-
|
48
|
+
elif (
|
49
|
+
isinstance(
|
50
|
+
connector,
|
51
|
+
IConnector,
|
52
|
+
)
|
53
|
+
or True
|
54
|
+
): # TODO: Get a valid protocol check here
|
37
55
|
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
56
|
else:
|
50
|
-
raise ValueError(
|
57
|
+
raise ValueError(
|
58
|
+
f"Invalid connector input: {type(connector)}",
|
59
|
+
)
|
51
60
|
|
52
61
|
args: list[str] = [verb]
|
53
62
|
if test_instance.config_path:
|
@@ -82,14 +91,14 @@ def run_test_job(
|
|
82
91
|
# Because it *also* can fail, we have ot redundantly wrap it in a try/except block.
|
83
92
|
|
84
93
|
result: entrypoint_wrapper.EntrypointOutput = entrypoint_wrapper._run_command( # noqa: SLF001 # Non-public API
|
85
|
-
source=connector_obj,
|
94
|
+
source=connector_obj, # type: ignore [arg-type]
|
86
95
|
args=args,
|
87
96
|
expecting_exception=test_instance.expect_exception,
|
88
97
|
)
|
89
98
|
if result.errors and not test_instance.expect_exception:
|
90
99
|
raise AssertionError(
|
91
100
|
"\n\n".join(
|
92
|
-
[str(err.trace.error).replace("\\n", "\n") for err in result.errors],
|
101
|
+
[str(err.trace.error).replace("\\n", "\n") for err in result.errors if err.trace],
|
93
102
|
)
|
94
103
|
)
|
95
104
|
|
@@ -106,10 +115,16 @@ def run_test_job(
|
|
106
115
|
+ "\n".join([str(msg) for msg in result.connection_status_messages])
|
107
116
|
)
|
108
117
|
if test_instance.expect_exception:
|
109
|
-
|
118
|
+
conn_status = result.connection_status_messages[0].connectionStatus
|
119
|
+
assert conn_status, (
|
120
|
+
"Expected CONNECTION_STATUS message to be present. Got: \n"
|
121
|
+
+ "\n".join([str(msg) for msg in result.connection_status_messages])
|
122
|
+
)
|
123
|
+
assert conn_status.status == Status.FAILED, (
|
110
124
|
"Expected CONNECTION_STATUS message to be FAILED. Got: \n"
|
111
125
|
+ "\n".join([str(msg) for msg in result.connection_status_messages])
|
112
126
|
)
|
127
|
+
|
113
128
|
return result
|
114
129
|
|
115
130
|
# For all other verbs, we assert check that an exception is raised (or not).
|
@@ -121,7 +136,14 @@ def run_test_job(
|
|
121
136
|
if result.errors:
|
122
137
|
raise AssertionError(
|
123
138
|
"\n\n".join(
|
124
|
-
[
|
139
|
+
[
|
140
|
+
str(err.trace.error).replace(
|
141
|
+
"\\n",
|
142
|
+
"\n",
|
143
|
+
)
|
144
|
+
for err in result.errors
|
145
|
+
if err.trace
|
146
|
+
],
|
125
147
|
)
|
126
148
|
)
|
127
149
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
"""Global pytest configuration for the Airbyte CDK tests."""
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
|
-
from typing import
|
4
|
+
from typing import cast
|
5
5
|
|
6
6
|
import pytest
|
7
7
|
|
8
8
|
|
9
|
-
def pytest_collect_file(parent:
|
9
|
+
def pytest_collect_file(parent: pytest.Module | None, path: Path) -> pytest.Module | None:
|
10
10
|
"""Collect test files based on their names."""
|
11
11
|
if path.name == "test_connector.py":
|
12
12
|
return cast(pytest.Module, pytest.Module.from_parent(parent, path=path))
|
@@ -41,6 +41,6 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
|
|
41
41
|
print(f"Setting up test: {item.name}")
|
42
42
|
|
43
43
|
|
44
|
-
def pytest_runtest_teardown(item: pytest.Item, nextitem:
|
44
|
+
def pytest_runtest_teardown(item: pytest.Item, nextitem: pytest.Item | None) -> None:
|
45
45
|
# This hook is called after each test function is executed
|
46
46
|
print(f"Tearing down test: {item.name}")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: airbyte-cdk
|
3
|
-
Version: 6.45.
|
3
|
+
Version: 6.45.1.post44.dev14417290834
|
4
4
|
Summary: A framework for writing Airbyte Connectors.
|
5
5
|
Home-page: https://airbyte.com
|
6
6
|
License: MIT
|
@@ -26,6 +26,7 @@ Requires-Dist: airbyte-protocol-models-dataclasses (>=0.14,<0.15)
|
|
26
26
|
Requires-Dist: anyascii (>=0.3.2,<0.4.0)
|
27
27
|
Requires-Dist: avro (>=1.11.2,<1.13.0) ; extra == "file-based"
|
28
28
|
Requires-Dist: backoff
|
29
|
+
Requires-Dist: boltons (>=25.0.0,<26.0.0)
|
29
30
|
Requires-Dist: cachetools
|
30
31
|
Requires-Dist: cohere (==4.21) ; extra == "vector-db-based"
|
31
32
|
Requires-Dist: cryptography (>=44.0.0,<45.0.0)
|
@@ -7,13 +7,13 @@ airbyte_cdk/config_observation.py,sha256=7SSPxtN0nXPkm4euGNcTTr1iLbwUL01jy-24V1H
|
|
7
7
|
airbyte_cdk/connector.py,sha256=bO23kdGRkl8XKFytOgrrWFc_VagteTHVEF6IsbizVkM,4224
|
8
8
|
airbyte_cdk/connector_builder/README.md,sha256=Hw3wvVewuHG9-QgsAq1jDiKuLlStDxKBz52ftyNRnBw,1665
|
9
9
|
airbyte_cdk/connector_builder/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
10
|
-
airbyte_cdk/connector_builder/connector_builder_handler.py,sha256=
|
11
|
-
airbyte_cdk/connector_builder/main.py,sha256=
|
10
|
+
airbyte_cdk/connector_builder/connector_builder_handler.py,sha256=ZQE7FU41TxHMqp40BGeEsO86Vvn1C6VkZRpgWKswgh4,6048
|
11
|
+
airbyte_cdk/connector_builder/main.py,sha256=j1pP5N8RsnvQZ4iYxhLdLEHsJ5Ui7IVFBUi6wYMGBkM,3839
|
12
12
|
airbyte_cdk/connector_builder/models.py,sha256=9pIZ98LW_d6fRS39VdnUOf3cxGt4TkC5MJ0_OrzcCRk,1578
|
13
13
|
airbyte_cdk/connector_builder/test_reader/__init__.py,sha256=iTwBMoI9vaJotEgpqZbFjlxRcbxXYypSVJ9YxeHk7wc,120
|
14
14
|
airbyte_cdk/connector_builder/test_reader/helpers.py,sha256=Iczn-_iczS2CaIAunWwyFcX0uLTra8Wh9JVfzm1Gfxo,26765
|
15
15
|
airbyte_cdk/connector_builder/test_reader/message_grouper.py,sha256=84BAEPIBHMq3WCfO14WNvh_q7OsjGgDt0q1FTu8eW-w,6918
|
16
|
-
airbyte_cdk/connector_builder/test_reader/reader.py,sha256=
|
16
|
+
airbyte_cdk/connector_builder/test_reader/reader.py,sha256=qcyCbAZ0Drk994-3QCjjV6t9f1XcvWmhprxvvhGszds,20765
|
17
17
|
airbyte_cdk/connector_builder/test_reader/types.py,sha256=hPZG3jO03kBaPyW94NI3JHRS1jxXGSNBcN1HFzOxo5Y,2528
|
18
18
|
airbyte_cdk/destinations/__init__.py,sha256=FyDp28PT_YceJD5HDFhA-mrGfX9AONIyMQ4d68CHNxQ,213
|
19
19
|
airbyte_cdk/destinations/destination.py,sha256=CIq-yb8C_0QvcKCtmStaHfiqn53GEfRAIGGCkJhKP1Q,5880
|
@@ -39,7 +39,7 @@ airbyte_cdk/sources/__init__.py,sha256=45J83QsFH3Wky3sVapZWg4C58R_i1thm61M06t2c1
|
|
39
39
|
airbyte_cdk/sources/abstract_source.py,sha256=50vxEBRByiNhT4WJkiFvgM-C6PWqKSJgvuNC_aeg2cw,15547
|
40
40
|
airbyte_cdk/sources/concurrent_source/__init__.py,sha256=3D_RJsxQfiLboSCDdNei1Iv-msRp3DXsas6E9kl7dXc,386
|
41
41
|
airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py,sha256=dbDBNcNNg2IZU5pZb3HfZeILU7X5_EhYGSbNqq3JD4I,12711
|
42
|
-
airbyte_cdk/sources/concurrent_source/concurrent_source.py,sha256=
|
42
|
+
airbyte_cdk/sources/concurrent_source/concurrent_source.py,sha256=P8B6EcLKaSstfAD9kDZsTJ0q8vRmdFrxLt-zOA5_By0,7737
|
43
43
|
airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py,sha256=f9PIRPWn2tXu0-bxVeYHL2vYdqCzZ_kgpHg5_Ep-cfQ,6103
|
44
44
|
airbyte_cdk/sources/concurrent_source/partition_generation_completed_sentinel.py,sha256=z1t-rAZBsqVidv2fpUlPHE9JgyXsITuGk4AMu96mXSQ,696
|
45
45
|
airbyte_cdk/sources/concurrent_source/stream_thread_exception.py,sha256=-q6mG2145HKQ28rZGD1bUmjPlIZ1S7-Yhewl8Ntu6xI,764
|
@@ -101,7 +101,7 @@ airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py,sha256=9IAJT
|
|
101
101
|
airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py,sha256=2YBOA2NnwAeIKlIhSwUB_W-FaGnPcmrG_liY7b4mV2Y,8365
|
102
102
|
airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py,sha256=10LFv1QPM-agVKl6eaANmEBOfd7gZgBrkoTcMggsieQ,4809
|
103
103
|
airbyte_cdk/sources/declarative/interpolation/__init__.py,sha256=Kh7FxhfetyNVDnAQ9zSxNe4oUbb8CvoW7Mqz7cs2iPg,437
|
104
|
-
airbyte_cdk/sources/declarative/interpolation/filters.py,sha256=
|
104
|
+
airbyte_cdk/sources/declarative/interpolation/filters.py,sha256=cYap5zzOxIJWCLIfbkNlpyfUhjZ8FklLroIG4WGzYVs,5537
|
105
105
|
airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py,sha256=8F3ntT_Mfo8cO9n6dCq8rTfJIpfKmzRCsVtVdhzaoGc,1964
|
106
106
|
airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py,sha256=h36RIng4GZ9v4o_fRmgJjTNOtWmhK7NOILU1oSKPE4Q,2083
|
107
107
|
airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py,sha256=vjwvkLk7_l6YDcFClwjCMcTleRjQBh7-dzny7PUaoG8,1857
|
@@ -222,11 +222,11 @@ airbyte_cdk/sources/file_based/discovery_policy/__init__.py,sha256=gl3ey6mZbyfra
|
|
222
222
|
airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py,sha256=dCfXX529Rd5rtopg4VeEgTPJjFtqjtjzPq6LCw18Wt0,605
|
223
223
|
airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py,sha256=-xujTidtrq6HC00WKbjQh1CZdT5LMuzkp5BLjqDmfTY,1007
|
224
224
|
airbyte_cdk/sources/file_based/exceptions.py,sha256=WP0qkG6fpWoBpOyyicgp5YNE393VWyegq5qSy0v4QtM,7362
|
225
|
-
airbyte_cdk/sources/file_based/file_based_source.py,sha256=
|
225
|
+
airbyte_cdk/sources/file_based/file_based_source.py,sha256=Xg8OYWnGc-OcVBglvS08uwAWGWHBhEqsBnyODIkOK-4,20051
|
226
226
|
airbyte_cdk/sources/file_based/file_based_stream_permissions_reader.py,sha256=4e7FXqQ9hueacexC0SyrZyjF8oREYHza8pKF9CgKbD8,5050
|
227
227
|
airbyte_cdk/sources/file_based/file_based_stream_reader.py,sha256=0cmppYO3pZlFiJrs5oorF4JXv4ErhOeEMrdLG7P-Gdk,6742
|
228
228
|
airbyte_cdk/sources/file_based/file_types/__init__.py,sha256=blCLn0-2LC-ZdgcNyDEhqM2RiUvEjEBh-G4-t32ZtuM,1268
|
229
|
-
airbyte_cdk/sources/file_based/file_types/avro_parser.py,sha256=
|
229
|
+
airbyte_cdk/sources/file_based/file_types/avro_parser.py,sha256=USEYqiICXBWpDV443VtNOCmUA-GINzY_Zah74_5w3qQ,10860
|
230
230
|
airbyte_cdk/sources/file_based/file_types/csv_parser.py,sha256=QlCXB-ry3np67Q_VerQEPoWDOTcPTB6Go4ydZxY9ae4,20445
|
231
231
|
airbyte_cdk/sources/file_based/file_types/excel_parser.py,sha256=BeplCq0hmojELU6bZCvvpRLpQ9us81TqbGYwrhd3INo,7188
|
232
232
|
airbyte_cdk/sources/file_based/file_types/file_transfer.py,sha256=HyGRihJxcb_lEsffKhfF3eylLBDy51_PXSwGUFEJ5bA,1265
|
@@ -242,7 +242,7 @@ airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validat
|
|
242
242
|
airbyte_cdk/sources/file_based/stream/__init__.py,sha256=q_zmeOHHg0JK5j1YNSOIsyXGz-wlTl_0E8z5GKVAcVM,543
|
243
243
|
airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py,sha256=9pQh3BHYcxm8CRC8XawfmBxL8O9HggpWwCCbX_ncINE,7509
|
244
244
|
airbyte_cdk/sources/file_based/stream/concurrent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
245
|
-
airbyte_cdk/sources/file_based/stream/concurrent/adapters.py,sha256=
|
245
|
+
airbyte_cdk/sources/file_based/stream/concurrent/adapters.py,sha256=EwWuoCsQRNaoizxbb2-BYVwRYOxr57exw3q3M6zVv1E,13931
|
246
246
|
airbyte_cdk/sources/file_based/stream/concurrent/cursor/__init__.py,sha256=Rx7TwjH8B7e0eee83Tlqxv1bWn-BVXOmlUAH7auM1uM,344
|
247
247
|
airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py,sha256=5dYZMLBEbvCyrCT89lCYdm2FdrLPLuxjdpQSVGP5o0w,1856
|
248
248
|
airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py,sha256=gRTL-9I3ejjQOpLKd6ixe9rB3kGlubCdhUt9ri6AdAI,14880
|
@@ -332,20 +332,20 @@ airbyte_cdk/sql/exceptions.py,sha256=7_-K2c_trPy6kM89I2pwsrnVEtXqOspd9Eqrzf2KD2A
|
|
332
332
|
airbyte_cdk/sql/secrets.py,sha256=FRIafU5YbWzoK8jtAcfExwzMGdswMbs0OOo1O7Y5i-g,4345
|
333
333
|
airbyte_cdk/sql/shared/__init__.py,sha256=-BU9zpzwx7JxSlS7EmFuGmdB9jK_QhhEJUe5dxErrDw,334
|
334
334
|
airbyte_cdk/sql/shared/catalog_providers.py,sha256=qiahORhtN6qBUGHhSKmzE00uC4i6W8unyBKCj7Kw47s,5218
|
335
|
-
airbyte_cdk/sql/shared/sql_processor.py,sha256=
|
335
|
+
airbyte_cdk/sql/shared/sql_processor.py,sha256=1CwfC3fp9dWnHBpKtly7vGduf9ho_MahiwxGFcULG3Y,27687
|
336
336
|
airbyte_cdk/sql/types.py,sha256=XEIhRAo_ASd0kVLBkdLf5bHiRhNple-IJrC9TibcDdY,5880
|
337
337
|
airbyte_cdk/test/__init__.py,sha256=f_XdkOg4_63QT2k3BbKY34209lppwgw-svzfZstQEq4,199
|
338
338
|
airbyte_cdk/test/catalog_builder.py,sha256=-y05Cz1x0Dlk6oE9LSKhCozssV2gYBNtMdV5YYOPOtk,3015
|
339
339
|
airbyte_cdk/test/declarative/__init__.py,sha256=IpPLFCCWnlpp_eTGKK_2WGRFfenbnolxYLmkQSZMO3U,205
|
340
340
|
airbyte_cdk/test/declarative/models/__init__.py,sha256=rXoywbDd-oqEhRQLuaEQIDxykMeawHjzzeN3XIpGQN8,132
|
341
341
|
airbyte_cdk/test/declarative/models/scenario.py,sha256=ySzQosxi2TRaw7JbQMFsuBieoH0ufXJhEY8Vu3DXtx4,2378
|
342
|
-
airbyte_cdk/test/declarative/test_suites/__init__.py,sha256=
|
343
|
-
airbyte_cdk/test/declarative/test_suites/connector_base.py,sha256=
|
344
|
-
airbyte_cdk/test/declarative/test_suites/declarative_sources.py,sha256=
|
342
|
+
airbyte_cdk/test/declarative/test_suites/__init__.py,sha256=ELgLwaAQ6Gl0RWW5qZleOGeiNvx4o9ldR7JUbwJ9i28,797
|
343
|
+
airbyte_cdk/test/declarative/test_suites/connector_base.py,sha256=NyXjroCMOp7VrEMqQxH9SzW6Ti2Om3B2ErRRXKDOTLA,7212
|
344
|
+
airbyte_cdk/test/declarative/test_suites/declarative_sources.py,sha256=CZW5EDBUZkT10bdTSalFwo-P9ARv8z5V6YsrJdhUzjk,2485
|
345
345
|
airbyte_cdk/test/declarative/test_suites/destination_base.py,sha256=L_l1gpNCkzfx-c7mzS1He5hTVbqR39OnfWdrYMglS7E,486
|
346
|
-
airbyte_cdk/test/declarative/test_suites/source_base.py,sha256=
|
346
|
+
airbyte_cdk/test/declarative/test_suites/source_base.py,sha256=SY6yaJ0qghdlwcw5-U04Ni0Hh2-HuqRsDz24cz6iinQ,4575
|
347
347
|
airbyte_cdk/test/declarative/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
348
|
-
airbyte_cdk/test/declarative/utils/job_runner.py,sha256=
|
348
|
+
airbyte_cdk/test/declarative/utils/job_runner.py,sha256=oIH5YyUOMav7K-YESn-aLgusSOccDFn67P2vsoaAnrk,5480
|
349
349
|
airbyte_cdk/test/entrypoint_wrapper.py,sha256=TyUmVJyIuGelAv6y8Wy_BnwqIRw_drjfZWKlroljCuQ,9951
|
350
350
|
airbyte_cdk/test/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
351
351
|
airbyte_cdk/test/fixtures/auto.py,sha256=wYKu1pBUJX60Bg-K2RwNXi11Txu1Rncc8WNlOvy9Zug,354
|
@@ -355,7 +355,7 @@ airbyte_cdk/test/mock_http/mocker.py,sha256=XgsjMtVoeMpRELPyALgrkHFauH9H5irxrz1K
|
|
355
355
|
airbyte_cdk/test/mock_http/request.py,sha256=tdB8cqk2vLgCDTOKffBKsM06llYs4ZecgtH6DKyx6yY,4112
|
356
356
|
airbyte_cdk/test/mock_http/response.py,sha256=s4-cQQqTtmeej0pQDWqmG0vUWpHS-93lIWMpW3zSVyU,662
|
357
357
|
airbyte_cdk/test/mock_http/response_builder.py,sha256=debPx_lRYBaQVSwCoKLa0F8KFk3h0qG7bWxFBATa0cc,7958
|
358
|
-
airbyte_cdk/test/pytest_config/plugin.py,sha256=
|
358
|
+
airbyte_cdk/test/pytest_config/plugin.py,sha256=iAZEnBKRrnIDqrRRh2NTPlQAKiIU2Z4yczx1tio8FKo,1447
|
359
359
|
airbyte_cdk/test/state_builder.py,sha256=kLPql9lNzUJaBg5YYRLJlY_Hy5JLHJDVyKPMZMoYM44,946
|
360
360
|
airbyte_cdk/test/utils/__init__.py,sha256=Hu-1XT2KDoYjDF7-_ziDwv5bY3PueGjANOCbzeOegDg,57
|
361
361
|
airbyte_cdk/test/utils/data.py,sha256=CkCR1_-rujWNmPXFR1IXTMwx1rAl06wAyIKWpDcN02w,820
|
@@ -379,9 +379,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
|
|
379
379
|
airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
|
380
380
|
airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
|
381
381
|
airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
|
382
|
-
airbyte_cdk-6.45.
|
383
|
-
airbyte_cdk-6.45.
|
384
|
-
airbyte_cdk-6.45.
|
385
|
-
airbyte_cdk-6.45.
|
386
|
-
airbyte_cdk-6.45.
|
387
|
-
airbyte_cdk-6.45.
|
382
|
+
airbyte_cdk-6.45.1.post44.dev14417290834.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
|
383
|
+
airbyte_cdk-6.45.1.post44.dev14417290834.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
|
384
|
+
airbyte_cdk-6.45.1.post44.dev14417290834.dist-info/METADATA,sha256=jw0gJWzbE5gnVhH7xr3fpWIRqIGYWfUJ0JMwvNAmdxo,6135
|
385
|
+
airbyte_cdk-6.45.1.post44.dev14417290834.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
386
|
+
airbyte_cdk-6.45.1.post44.dev14417290834.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
|
387
|
+
airbyte_cdk-6.45.1.post44.dev14417290834.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|