airbyte-internal-ops 0.4.2__py3-none-any.whl → 0.5.0__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_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.0.dist-info}/METADATA +1 -1
- {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.0.dist-info}/RECORD +13 -52
- airbyte_ops_mcp/cli/cloud.py +27 -0
- airbyte_ops_mcp/cloud_admin/api_client.py +473 -0
- airbyte_ops_mcp/cloud_admin/models.py +56 -0
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +460 -0
- airbyte_ops_mcp/mcp/prerelease.py +5 -44
- airbyte_ops_mcp/regression_tests/ci_output.py +8 -4
- airbyte_ops_mcp/regression_tests/http_metrics.py +21 -2
- airbyte_ops_mcp/regression_tests/models.py +6 -0
- airbyte_ops_mcp/telemetry.py +162 -0
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/.gitignore +0 -1
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/README.md +0 -420
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/__init__.py +0 -2
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/__init__.py +0 -1
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/__init__.py +0 -8
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/base_backend.py +0 -16
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/duckdb_backend.py +0 -87
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/file_backend.py +0 -165
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connection_objects_retrieval.py +0 -377
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connector_runner.py +0 -247
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/errors.py +0 -7
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/evaluation_modes.py +0 -25
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/hacks.py +0 -23
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/json_schema_helper.py +0 -384
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/mitm_addons.py +0 -37
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/models.py +0 -595
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/proxy.py +0 -207
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/secret_access.py +0 -47
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/segment_tracking.py +0 -45
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/utils.py +0 -214
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/conftest.py.disabled +0 -751
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/consts.py +0 -4
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/poetry.lock +0 -4480
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/pytest.ini +0 -9
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/__init__.py +0 -1
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_check.py +0 -61
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_discover.py +0 -117
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_read.py +0 -627
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_spec.py +0 -43
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/report.py +0 -542
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/stash_keys.py +0 -38
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/__init__.py +0 -0
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/private_details.html.j2 +0 -305
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/report.html.j2 +0 -515
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/utils.py +0 -187
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/__init__.py +0 -0
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_check.py +0 -61
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_discover.py +0 -217
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_read.py +0 -177
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_spec.py +0 -631
- {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.0.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import datetime
|
|
5
|
-
import json
|
|
6
|
-
from abc import ABC, abstractmethod
|
|
7
|
-
from collections import defaultdict
|
|
8
|
-
from collections.abc import Iterable, MutableMapping
|
|
9
|
-
from copy import deepcopy
|
|
10
|
-
from enum import Enum
|
|
11
|
-
from functools import cache
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import TYPE_CHECKING, Any, Optional
|
|
14
|
-
|
|
15
|
-
import requests
|
|
16
|
-
import yaml
|
|
17
|
-
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
18
|
-
from live_tests import stash_keys
|
|
19
|
-
from live_tests.commons.models import Command, ConnectionObjects
|
|
20
|
-
from live_tests.consts import MAX_LINES_IN_REPORT
|
|
21
|
-
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
from typing import List
|
|
24
|
-
|
|
25
|
-
import pytest
|
|
26
|
-
from _pytest.config import Config
|
|
27
|
-
from airbyte_protocol.models import (
|
|
28
|
-
AirbyteStream,
|
|
29
|
-
ConfiguredAirbyteStream,
|
|
30
|
-
SyncMode,
|
|
31
|
-
Type,
|
|
32
|
-
) # type: ignore
|
|
33
|
-
from live_tests.commons.models import ExecutionResult
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class ReportState(Enum):
|
|
37
|
-
INITIALIZING = "initializing"
|
|
38
|
-
RUNNING = "running"
|
|
39
|
-
FINISHED = "finished"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class BaseReport(ABC):
|
|
43
|
-
TEMPLATE_NAME: str
|
|
44
|
-
|
|
45
|
-
def __init__(self, path: Path, pytest_config: Config) -> None:
|
|
46
|
-
self.path = path
|
|
47
|
-
self.pytest_config = pytest_config
|
|
48
|
-
self.created_at = datetime.datetime.utcnow()
|
|
49
|
-
self.updated_at = self.created_at
|
|
50
|
-
|
|
51
|
-
self.control_execution_results_per_command: dict[
|
|
52
|
-
Command, List[ExecutionResult]
|
|
53
|
-
] = {command: [] for command in Command}
|
|
54
|
-
self.target_execution_results_per_command: dict[
|
|
55
|
-
Command, List[ExecutionResult]
|
|
56
|
-
] = {command: [] for command in Command}
|
|
57
|
-
self.update(ReportState.INITIALIZING)
|
|
58
|
-
|
|
59
|
-
@abstractmethod
|
|
60
|
-
def render(self) -> None:
|
|
61
|
-
pass
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def all_connection_objects(self) -> List[ConnectionObjects]:
|
|
65
|
-
return self.pytest_config.stash[stash_keys.ALL_CONNECTION_OBJECTS]
|
|
66
|
-
|
|
67
|
-
def add_control_execution_result(
|
|
68
|
-
self, control_execution_result: ExecutionResult
|
|
69
|
-
) -> None:
|
|
70
|
-
self.control_execution_results_per_command[
|
|
71
|
-
control_execution_result.command
|
|
72
|
-
].append(control_execution_result)
|
|
73
|
-
self.update()
|
|
74
|
-
|
|
75
|
-
def add_target_execution_result(
|
|
76
|
-
self, target_execution_result: ExecutionResult
|
|
77
|
-
) -> None:
|
|
78
|
-
self.target_execution_results_per_command[
|
|
79
|
-
target_execution_result.command
|
|
80
|
-
].append(target_execution_result)
|
|
81
|
-
self.update()
|
|
82
|
-
|
|
83
|
-
def update(self, state: ReportState = ReportState.RUNNING) -> None:
|
|
84
|
-
self._state = state
|
|
85
|
-
self.updated_at = datetime.datetime.utcnow()
|
|
86
|
-
self.render()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class PrivateDetailsReport(BaseReport):
|
|
90
|
-
TEMPLATE_NAME = "private_details.html.j2"
|
|
91
|
-
SPEC_SECRET_MASK_URL = (
|
|
92
|
-
"https://connectors.airbyte.com/files/registries/v0/specs_secrets_mask.yaml"
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
def __init__(self, path: Path, pytest_config: Config) -> None:
|
|
96
|
-
self.secret_properties = self.get_secret_properties()
|
|
97
|
-
super().__init__(path, pytest_config)
|
|
98
|
-
|
|
99
|
-
def get_secret_properties(self) -> list:
|
|
100
|
-
response = requests.get(self.SPEC_SECRET_MASK_URL)
|
|
101
|
-
response.raise_for_status()
|
|
102
|
-
return yaml.safe_load(response.text)["properties"]
|
|
103
|
-
|
|
104
|
-
def scrub_secrets_from_config(self, to_scrub: MutableMapping) -> MutableMapping:
|
|
105
|
-
if isinstance(to_scrub, dict):
|
|
106
|
-
for key, value in to_scrub.items():
|
|
107
|
-
if key in self.secret_properties:
|
|
108
|
-
to_scrub[key] = "********"
|
|
109
|
-
elif isinstance(value, dict):
|
|
110
|
-
to_scrub[key] = self.scrub_secrets_from_config(value)
|
|
111
|
-
return to_scrub
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
def renderable_connection_objects(self) -> list[dict[str, Any]]:
|
|
115
|
-
return [
|
|
116
|
-
{
|
|
117
|
-
"workspace_id": connection_objects.workspace_id,
|
|
118
|
-
"connection_id": connection_objects.connection_id,
|
|
119
|
-
"hashed_connection_id": connection_objects.hashed_connection_id,
|
|
120
|
-
"source_config": json.dumps(
|
|
121
|
-
self.scrub_secrets_from_config(
|
|
122
|
-
deepcopy(connection_objects.source_config.data)
|
|
123
|
-
if connection_objects.source_config
|
|
124
|
-
else {}
|
|
125
|
-
),
|
|
126
|
-
indent=2,
|
|
127
|
-
),
|
|
128
|
-
"url": connection_objects.url,
|
|
129
|
-
}
|
|
130
|
-
for connection_objects in self.all_connection_objects
|
|
131
|
-
]
|
|
132
|
-
|
|
133
|
-
def render(self) -> None:
|
|
134
|
-
jinja_env = Environment(
|
|
135
|
-
loader=PackageLoader(__package__, "templates"),
|
|
136
|
-
autoescape=select_autoescape(),
|
|
137
|
-
trim_blocks=False,
|
|
138
|
-
lstrip_blocks=True,
|
|
139
|
-
)
|
|
140
|
-
template = jinja_env.get_template(self.TEMPLATE_NAME)
|
|
141
|
-
rendered = template.render(
|
|
142
|
-
user=self.pytest_config.stash[stash_keys.USER],
|
|
143
|
-
test_date=self.created_at,
|
|
144
|
-
all_connection_objects=self.renderable_connection_objects,
|
|
145
|
-
connector_image=self.pytest_config.stash[stash_keys.CONNECTOR_IMAGE],
|
|
146
|
-
control_version=self.pytest_config.stash[stash_keys.CONTROL_VERSION],
|
|
147
|
-
target_version=self.pytest_config.stash[stash_keys.TARGET_VERSION],
|
|
148
|
-
requested_urls_per_command=self.get_requested_urls_per_command(),
|
|
149
|
-
fully_generated=self._state is ReportState.FINISHED,
|
|
150
|
-
)
|
|
151
|
-
self.path.write_text(rendered)
|
|
152
|
-
|
|
153
|
-
def get_requested_urls_per_command(
|
|
154
|
-
self,
|
|
155
|
-
) -> dict[Command, list[tuple[int, str, str]]]:
|
|
156
|
-
requested_urls_per_command = {}
|
|
157
|
-
all_commands = sorted(
|
|
158
|
-
list(
|
|
159
|
-
set(self.control_execution_results_per_command.keys()).union(
|
|
160
|
-
set(self.target_execution_results_per_command.keys())
|
|
161
|
-
)
|
|
162
|
-
),
|
|
163
|
-
key=lambda command: command.value,
|
|
164
|
-
)
|
|
165
|
-
for command in all_commands:
|
|
166
|
-
if command in self.control_execution_results_per_command:
|
|
167
|
-
control_flows = [
|
|
168
|
-
flow
|
|
169
|
-
for exec_result in self.control_execution_results_per_command[
|
|
170
|
-
command
|
|
171
|
-
]
|
|
172
|
-
for flow in exec_result.http_flows
|
|
173
|
-
]
|
|
174
|
-
else:
|
|
175
|
-
control_flows = []
|
|
176
|
-
if command in self.target_execution_results_per_command:
|
|
177
|
-
target_flows = [
|
|
178
|
-
flow
|
|
179
|
-
for exec_result in self.target_execution_results_per_command[
|
|
180
|
-
command
|
|
181
|
-
]
|
|
182
|
-
for flow in exec_result.http_flows
|
|
183
|
-
]
|
|
184
|
-
else:
|
|
185
|
-
target_flows = []
|
|
186
|
-
all_flows = []
|
|
187
|
-
max_flows = max(len(control_flows), len(target_flows))
|
|
188
|
-
for i in range(max_flows):
|
|
189
|
-
control_url = (
|
|
190
|
-
control_flows[i].request.url if i < len(control_flows) else ""
|
|
191
|
-
)
|
|
192
|
-
target_url = (
|
|
193
|
-
target_flows[i].request.url if i < len(target_flows) else ""
|
|
194
|
-
)
|
|
195
|
-
all_flows.append((i, control_url, target_url))
|
|
196
|
-
requested_urls_per_command[command] = all_flows
|
|
197
|
-
return requested_urls_per_command
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
class TestReport(BaseReport):
|
|
201
|
-
TEMPLATE_NAME = "report.html.j2"
|
|
202
|
-
|
|
203
|
-
def __init__(
|
|
204
|
-
self,
|
|
205
|
-
path: Path,
|
|
206
|
-
pytest_config: Config,
|
|
207
|
-
private_details_url: Optional[str] = None,
|
|
208
|
-
) -> None:
|
|
209
|
-
self.private_details_url = private_details_url
|
|
210
|
-
self.test_results: list[dict[str, Any]] = []
|
|
211
|
-
super().__init__(path, pytest_config)
|
|
212
|
-
|
|
213
|
-
def add_test_result(
|
|
214
|
-
self, test_report: pytest.TestReport, test_documentation: Optional[str] = None
|
|
215
|
-
) -> None:
|
|
216
|
-
cut_properties: list[tuple[str, str]] = []
|
|
217
|
-
for property_name, property_value in test_report.user_properties:
|
|
218
|
-
if len(str(property_value).splitlines()) > MAX_LINES_IN_REPORT:
|
|
219
|
-
cut_property_name = f"{property_name} (truncated)"
|
|
220
|
-
cut_property_value = "\n".join(
|
|
221
|
-
str(property_value).splitlines()[:MAX_LINES_IN_REPORT]
|
|
222
|
-
)
|
|
223
|
-
cut_property_value += f"\n... and {len(str(property_value).splitlines()) - MAX_LINES_IN_REPORT} more lines.\nPlease check the artifacts files for the full output."
|
|
224
|
-
cut_properties.append((cut_property_name, cut_property_value))
|
|
225
|
-
else:
|
|
226
|
-
cut_properties.append((property_name, str(property_value)))
|
|
227
|
-
self.test_results.append(
|
|
228
|
-
{
|
|
229
|
-
"name": test_report.head_line,
|
|
230
|
-
"result": test_report.outcome,
|
|
231
|
-
"output": test_report.longreprtext if test_report.longrepr else "",
|
|
232
|
-
"properties": cut_properties,
|
|
233
|
-
"documentation": test_documentation,
|
|
234
|
-
}
|
|
235
|
-
)
|
|
236
|
-
self.update()
|
|
237
|
-
|
|
238
|
-
def render(self) -> None:
|
|
239
|
-
jinja_env = Environment(
|
|
240
|
-
loader=PackageLoader(__package__, "templates"),
|
|
241
|
-
autoescape=select_autoescape(),
|
|
242
|
-
trim_blocks=False,
|
|
243
|
-
lstrip_blocks=True,
|
|
244
|
-
)
|
|
245
|
-
template = jinja_env.get_template(self.TEMPLATE_NAME)
|
|
246
|
-
rendered = template.render(
|
|
247
|
-
fully_generated=self._state is ReportState.FINISHED,
|
|
248
|
-
user=self.pytest_config.stash[stash_keys.USER],
|
|
249
|
-
test_date=self.updated_at,
|
|
250
|
-
connector_image=self.pytest_config.stash[stash_keys.CONNECTOR_IMAGE],
|
|
251
|
-
control_version=self.pytest_config.stash[stash_keys.CONTROL_VERSION],
|
|
252
|
-
target_version=self.pytest_config.stash[stash_keys.TARGET_VERSION],
|
|
253
|
-
all_connection_objects=self.renderable_connection_objects,
|
|
254
|
-
message_count_per_type=self.get_message_count_per_type(),
|
|
255
|
-
stream_coverage_metrics=self.get_stream_coverage_metrics(),
|
|
256
|
-
untested_streams=self.get_untested_streams(),
|
|
257
|
-
selected_streams=self.get_configured_streams(),
|
|
258
|
-
sync_mode_coverage=self.get_sync_mode_coverage(),
|
|
259
|
-
http_metrics_per_command=self.get_http_metrics_per_command(),
|
|
260
|
-
record_count_per_command_and_stream=self.get_record_count_per_stream(),
|
|
261
|
-
test_results=self.test_results,
|
|
262
|
-
max_lines=MAX_LINES_IN_REPORT,
|
|
263
|
-
private_details_url=self.private_details_url,
|
|
264
|
-
)
|
|
265
|
-
self.path.write_text(rendered)
|
|
266
|
-
|
|
267
|
-
@property
|
|
268
|
-
def renderable_connection_objects(self) -> list[dict[str, Any]]:
|
|
269
|
-
return [
|
|
270
|
-
{
|
|
271
|
-
"hashed_connection_id": connection_objects.hashed_connection_id,
|
|
272
|
-
"catalog": connection_objects.catalog.json(indent=2)
|
|
273
|
-
if connection_objects.catalog
|
|
274
|
-
else {},
|
|
275
|
-
"configured_catalog": connection_objects.configured_catalog.json(
|
|
276
|
-
indent=2
|
|
277
|
-
),
|
|
278
|
-
"state": json.dumps(
|
|
279
|
-
connection_objects.state if connection_objects.state else {},
|
|
280
|
-
indent=2,
|
|
281
|
-
),
|
|
282
|
-
}
|
|
283
|
-
for connection_objects in self.all_connection_objects
|
|
284
|
-
]
|
|
285
|
-
|
|
286
|
-
def get_stream_coverage_metrics(self) -> dict[str, str]:
|
|
287
|
-
configured_catalog_stream_count = len(self.get_configured_streams())
|
|
288
|
-
catalog_stream_count = len(self.all_streams)
|
|
289
|
-
coverage = (
|
|
290
|
-
configured_catalog_stream_count / catalog_stream_count
|
|
291
|
-
if catalog_stream_count > 0
|
|
292
|
-
else 0
|
|
293
|
-
)
|
|
294
|
-
return {
|
|
295
|
-
"Available in catalog": str(catalog_stream_count),
|
|
296
|
-
"In use (in configured catalog)": str(configured_catalog_stream_count),
|
|
297
|
-
"Coverage": f"{coverage * 100:.2f}%",
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
def get_record_count_per_stream(
|
|
301
|
-
self,
|
|
302
|
-
) -> dict[Command, dict[str, dict[str, int] | int]]:
|
|
303
|
-
record_count_per_command_and_stream: dict[
|
|
304
|
-
Command, dict[str, dict[str, int] | int]
|
|
305
|
-
] = {}
|
|
306
|
-
for control_results, target_results in zip(
|
|
307
|
-
self.control_execution_results_per_command.values(),
|
|
308
|
-
self.target_execution_results_per_command.values(),
|
|
309
|
-
strict=False,
|
|
310
|
-
):
|
|
311
|
-
per_stream_count = defaultdict(lambda: {"control": 0, "target": 0}) # type: ignore
|
|
312
|
-
for results, source in [
|
|
313
|
-
(control_results, "control"),
|
|
314
|
-
(target_results, "target"),
|
|
315
|
-
]:
|
|
316
|
-
stream_schemas: Iterable = [
|
|
317
|
-
stream_schema
|
|
318
|
-
for result in results
|
|
319
|
-
for stream_schema in result.stream_schemas
|
|
320
|
-
]
|
|
321
|
-
|
|
322
|
-
for stream in stream_schemas:
|
|
323
|
-
per_stream_count[stream][source] = sum(
|
|
324
|
-
[
|
|
325
|
-
self._get_record_count_for_stream(result, stream)
|
|
326
|
-
for result in results
|
|
327
|
-
]
|
|
328
|
-
)
|
|
329
|
-
for stream in per_stream_count:
|
|
330
|
-
per_stream_count[stream]["difference"] = (
|
|
331
|
-
per_stream_count[stream]["target"]
|
|
332
|
-
- per_stream_count[stream]["control"]
|
|
333
|
-
)
|
|
334
|
-
if control_results:
|
|
335
|
-
record_count_per_command_and_stream[control_results[0].command] = (
|
|
336
|
-
per_stream_count # type: ignore
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
return record_count_per_command_and_stream
|
|
340
|
-
|
|
341
|
-
@cache
|
|
342
|
-
def _get_record_count_for_stream(self, result: ExecutionResult, stream: str) -> int:
|
|
343
|
-
return sum(1 for _ in result.get_records_per_stream(stream)) # type: ignore
|
|
344
|
-
|
|
345
|
-
def get_untested_streams(self) -> list[str]:
|
|
346
|
-
streams_with_data: set[str] = set()
|
|
347
|
-
for stream_count in self.get_record_count_per_stream().values():
|
|
348
|
-
streams_with_data.update(stream_count.keys())
|
|
349
|
-
|
|
350
|
-
return [
|
|
351
|
-
stream.name
|
|
352
|
-
for stream in self.all_streams
|
|
353
|
-
if stream.name not in streams_with_data
|
|
354
|
-
]
|
|
355
|
-
|
|
356
|
-
@property
|
|
357
|
-
def all_streams(self) -> List[AirbyteStream]:
|
|
358
|
-
# A set would be better but AirbyteStream is not hashable
|
|
359
|
-
all_streams = dict()
|
|
360
|
-
for connection_objects in self.all_connection_objects:
|
|
361
|
-
if connection_objects.catalog:
|
|
362
|
-
for stream in connection_objects.catalog.streams:
|
|
363
|
-
all_streams[stream.name] = stream
|
|
364
|
-
return list(all_streams.values())
|
|
365
|
-
|
|
366
|
-
@property
|
|
367
|
-
def all_configured_streams(self) -> List[ConfiguredAirbyteStream]:
|
|
368
|
-
all_configured_streams = dict()
|
|
369
|
-
for connection_objects in self.all_connection_objects:
|
|
370
|
-
if connection_objects.configured_catalog:
|
|
371
|
-
for (
|
|
372
|
-
configured_airbyte_stream
|
|
373
|
-
) in connection_objects.configured_catalog.streams:
|
|
374
|
-
all_configured_streams[configured_airbyte_stream.stream.name] = (
|
|
375
|
-
configured_airbyte_stream
|
|
376
|
-
)
|
|
377
|
-
return list(all_configured_streams.values())
|
|
378
|
-
|
|
379
|
-
def get_configured_streams(self) -> dict[str, dict[str, SyncMode | bool]]:
|
|
380
|
-
untested_streams = self.get_untested_streams()
|
|
381
|
-
return (
|
|
382
|
-
{
|
|
383
|
-
configured_stream.stream.name: {
|
|
384
|
-
"sync_mode": configured_stream.sync_mode,
|
|
385
|
-
"has_data": configured_stream.stream.name not in untested_streams,
|
|
386
|
-
}
|
|
387
|
-
for configured_stream in sorted(
|
|
388
|
-
self.all_configured_streams,
|
|
389
|
-
key=lambda x: x.stream.name,
|
|
390
|
-
)
|
|
391
|
-
}
|
|
392
|
-
if self.all_configured_streams
|
|
393
|
-
else {}
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
def get_sync_mode_coverage(self) -> dict[SyncMode, int]:
|
|
397
|
-
count_per_sync_mode: dict[SyncMode, int] = defaultdict(int)
|
|
398
|
-
for s in self.get_configured_streams().values():
|
|
399
|
-
count_per_sync_mode[s["sync_mode"]] += 1
|
|
400
|
-
return count_per_sync_mode
|
|
401
|
-
|
|
402
|
-
def get_message_count_per_type(
|
|
403
|
-
self,
|
|
404
|
-
) -> tuple[list[Command], dict[Type, dict[Command, dict[str, int]]]]:
|
|
405
|
-
message_count_per_type_and_command: dict[
|
|
406
|
-
Type, dict[Command, dict[str, int]]
|
|
407
|
-
] = {}
|
|
408
|
-
all_message_types = set()
|
|
409
|
-
all_commands = set()
|
|
410
|
-
# Gather all message types from both control and target execution reports
|
|
411
|
-
for execution_results_per_command in [
|
|
412
|
-
self.control_execution_results_per_command,
|
|
413
|
-
self.target_execution_results_per_command,
|
|
414
|
-
]:
|
|
415
|
-
for command, execution_results in execution_results_per_command.items():
|
|
416
|
-
all_commands.add(command)
|
|
417
|
-
for execution_result in execution_results:
|
|
418
|
-
for (
|
|
419
|
-
message_type
|
|
420
|
-
) in execution_result.get_message_count_per_type().keys():
|
|
421
|
-
all_message_types.add(message_type)
|
|
422
|
-
|
|
423
|
-
all_commands_sorted = sorted(all_commands, key=lambda command: command.value)
|
|
424
|
-
all_message_types_sorted = sorted(
|
|
425
|
-
all_message_types, key=lambda message_type: message_type.value
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
# Iterate over all message types and commands to count messages
|
|
429
|
-
for message_type in all_message_types_sorted:
|
|
430
|
-
message_count_per_type_and_command[message_type] = {}
|
|
431
|
-
for command in all_commands_sorted:
|
|
432
|
-
message_count_per_type_and_command[message_type][command] = {
|
|
433
|
-
"control": 0,
|
|
434
|
-
"target": 0,
|
|
435
|
-
}
|
|
436
|
-
if command in self.control_execution_results_per_command:
|
|
437
|
-
for control_result in self.control_execution_results_per_command[
|
|
438
|
-
command
|
|
439
|
-
]:
|
|
440
|
-
message_count_per_type_and_command[message_type][command][
|
|
441
|
-
"control"
|
|
442
|
-
] += control_result.get_message_count_per_type().get(
|
|
443
|
-
message_type, 0
|
|
444
|
-
)
|
|
445
|
-
if command in self.target_execution_results_per_command:
|
|
446
|
-
for target_result in self.target_execution_results_per_command[
|
|
447
|
-
command
|
|
448
|
-
]:
|
|
449
|
-
message_count_per_type_and_command[message_type][command][
|
|
450
|
-
"target"
|
|
451
|
-
] += target_result.get_message_count_per_type().get(
|
|
452
|
-
message_type, 0
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
message_count_per_type_and_command[message_type][command][
|
|
456
|
-
"difference"
|
|
457
|
-
] = (
|
|
458
|
-
message_count_per_type_and_command[message_type][command]["target"]
|
|
459
|
-
- message_count_per_type_and_command[message_type][command][
|
|
460
|
-
"control"
|
|
461
|
-
]
|
|
462
|
-
)
|
|
463
|
-
return all_commands_sorted, message_count_per_type_and_command
|
|
464
|
-
|
|
465
|
-
def get_http_metrics_per_command(
|
|
466
|
-
self,
|
|
467
|
-
) -> dict[Command, dict[str, dict[str, int | str] | int]]:
|
|
468
|
-
metrics_per_command: dict[Command, dict[str, dict[str, int | str] | int]] = {}
|
|
469
|
-
|
|
470
|
-
for control_results, target_results in zip(
|
|
471
|
-
self.control_execution_results_per_command.values(),
|
|
472
|
-
self.target_execution_results_per_command.values(),
|
|
473
|
-
strict=False,
|
|
474
|
-
):
|
|
475
|
-
# TODO
|
|
476
|
-
# Duplicate flow counts may be wrong when we gather results from multiple connections
|
|
477
|
-
control_flow_count = sum(
|
|
478
|
-
[len(control_result.http_flows) for control_result in control_results]
|
|
479
|
-
)
|
|
480
|
-
control_all_urls = [
|
|
481
|
-
f.request.url
|
|
482
|
-
for control_result in control_results
|
|
483
|
-
for f in control_result.http_flows
|
|
484
|
-
]
|
|
485
|
-
control_duplicate_flow_count = len(control_all_urls) - len(
|
|
486
|
-
set(control_all_urls)
|
|
487
|
-
)
|
|
488
|
-
control_cache_hits_count = sum(
|
|
489
|
-
1
|
|
490
|
-
for control_result in control_results
|
|
491
|
-
for f in control_result.http_flows
|
|
492
|
-
if f.is_replay
|
|
493
|
-
)
|
|
494
|
-
control_cache_hit_ratio = (
|
|
495
|
-
f"{(control_cache_hits_count / control_flow_count) * 100:.2f}%"
|
|
496
|
-
if control_flow_count != 0
|
|
497
|
-
else "N/A"
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
target_flow_count = sum(
|
|
501
|
-
[len(target_result.http_flows) for target_result in target_results]
|
|
502
|
-
)
|
|
503
|
-
target_all_urls = [
|
|
504
|
-
f.request.url
|
|
505
|
-
for target_result in target_results
|
|
506
|
-
for f in target_result.http_flows
|
|
507
|
-
]
|
|
508
|
-
target_duplicate_flow_count = len(target_all_urls) - len(
|
|
509
|
-
set(target_all_urls)
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
target_cache_hits_count = sum(
|
|
513
|
-
1
|
|
514
|
-
for target_result in target_results
|
|
515
|
-
for f in target_result.http_flows
|
|
516
|
-
if f.is_replay
|
|
517
|
-
)
|
|
518
|
-
target_cache_hit_ratio = (
|
|
519
|
-
f"{(target_cache_hits_count / target_flow_count) * 100:.2f}%"
|
|
520
|
-
if target_flow_count != 0
|
|
521
|
-
else "N/A"
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
flow_count_difference = target_flow_count - control_flow_count
|
|
525
|
-
if control_results:
|
|
526
|
-
metrics_per_command[control_results[0].command] = {
|
|
527
|
-
"control": {
|
|
528
|
-
"flow_count": control_flow_count,
|
|
529
|
-
"duplicate_flow_count": control_duplicate_flow_count,
|
|
530
|
-
"cache_hits_count": control_cache_hits_count,
|
|
531
|
-
"cache_hit_ratio": control_cache_hit_ratio,
|
|
532
|
-
},
|
|
533
|
-
"target": {
|
|
534
|
-
"flow_count": target_flow_count,
|
|
535
|
-
"duplicate_flow_count": target_duplicate_flow_count,
|
|
536
|
-
"cache_hits_count": target_cache_hits_count,
|
|
537
|
-
"cache_hit_ratio": target_cache_hit_ratio,
|
|
538
|
-
},
|
|
539
|
-
"difference": flow_count_difference,
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return metrics_per_command
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import List
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
from live_tests.commons.evaluation_modes import TestEvaluationMode
|
|
9
|
-
from live_tests.commons.models import ConnectionObjects, ConnectionSubset
|
|
10
|
-
from live_tests.report import PrivateDetailsReport, TestReport
|
|
11
|
-
|
|
12
|
-
AIRBYTE_API_KEY = pytest.StashKey[str]()
|
|
13
|
-
AUTO_SELECT_CONNECTION = pytest.StashKey[bool]()
|
|
14
|
-
ALL_CONNECTION_OBJECTS = pytest.StashKey[List[ConnectionObjects]]()
|
|
15
|
-
CONNECTION_URL = pytest.StashKey[str | None]()
|
|
16
|
-
CONNECTOR_IMAGE = pytest.StashKey[str]()
|
|
17
|
-
CONTROL_VERSION = pytest.StashKey[str]()
|
|
18
|
-
CONNECTION_SUBSET = pytest.StashKey[ConnectionSubset]()
|
|
19
|
-
DAGGER_LOG_PATH = pytest.StashKey[Path]()
|
|
20
|
-
DUCKDB_PATH = pytest.StashKey[Path]()
|
|
21
|
-
HTTP_DUMP_CACHE_VOLUMES = pytest.StashKey[list]()
|
|
22
|
-
RUN_IN_AIRBYTE_CI = pytest.StashKey[bool]() # Running in airbyte-ci, locally or in GhA
|
|
23
|
-
IS_PRODUCTION_CI = pytest.StashKey[bool]() # Running in airbyte-ci in GhA
|
|
24
|
-
IS_PERMITTED_BOOL = pytest.StashKey[bool]()
|
|
25
|
-
PR_URL = pytest.StashKey[str]()
|
|
26
|
-
TEST_REPORT = pytest.StashKey[TestReport]()
|
|
27
|
-
PRIVATE_DETAILS_REPORT = pytest.StashKey[PrivateDetailsReport]()
|
|
28
|
-
RETRIEVAL_REASONS = pytest.StashKey[str]()
|
|
29
|
-
SELECTED_STREAMS = pytest.StashKey[set[str]]()
|
|
30
|
-
SESSION_RUN_ID = pytest.StashKey[str]()
|
|
31
|
-
SHOULD_READ_WITH_STATE = pytest.StashKey[bool]()
|
|
32
|
-
DISABLE_PROXY = pytest.StashKey[bool]()
|
|
33
|
-
TARGET_VERSION = pytest.StashKey[str]()
|
|
34
|
-
TEST_ARTIFACT_DIRECTORY = pytest.StashKey[Path]()
|
|
35
|
-
USER = pytest.StashKey[str]()
|
|
36
|
-
WORKSPACE_ID = pytest.StashKey[str]()
|
|
37
|
-
TEST_EVALUATION_MODE = pytest.StashKey[TestEvaluationMode]
|
|
38
|
-
MAX_CONNECTIONS = pytest.StashKey[int | None]()
|
|
File without changes
|