airbyte-internal-ops 0.4.2__py3-none-any.whl → 0.5.1__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.
Files changed (130) hide show
  1. {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/METADATA +2 -1
  2. {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/RECORD +21 -129
  3. airbyte_ops_mcp/cli/cloud.py +31 -2
  4. airbyte_ops_mcp/cloud_admin/api_client.py +506 -33
  5. airbyte_ops_mcp/cloud_admin/models.py +56 -0
  6. airbyte_ops_mcp/constants.py +58 -0
  7. airbyte_ops_mcp/{_legacy/airbyte_ci/metadata_service/docker_hub.py → docker_hub.py} +16 -10
  8. airbyte_ops_mcp/mcp/cloud_connector_versions.py +491 -10
  9. airbyte_ops_mcp/mcp/prerelease.py +5 -44
  10. airbyte_ops_mcp/mcp/prod_db_queries.py +128 -4
  11. airbyte_ops_mcp/mcp/regression_tests.py +10 -5
  12. airbyte_ops_mcp/{_legacy/airbyte_ci/metadata_service/validators/metadata_validator.py → metadata_validator.py} +18 -12
  13. airbyte_ops_mcp/prod_db_access/queries.py +51 -0
  14. airbyte_ops_mcp/prod_db_access/sql.py +76 -0
  15. airbyte_ops_mcp/regression_tests/ci_output.py +8 -4
  16. airbyte_ops_mcp/regression_tests/connection_fetcher.py +16 -5
  17. airbyte_ops_mcp/regression_tests/http_metrics.py +21 -2
  18. airbyte_ops_mcp/regression_tests/models.py +7 -1
  19. airbyte_ops_mcp/telemetry.py +162 -0
  20. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/.gitignore +0 -1
  21. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/README.md +0 -420
  22. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/__init__.py +0 -2
  23. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/__init__.py +0 -1
  24. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/__init__.py +0 -8
  25. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/base_backend.py +0 -16
  26. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/duckdb_backend.py +0 -87
  27. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/file_backend.py +0 -165
  28. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connection_objects_retrieval.py +0 -377
  29. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connector_runner.py +0 -247
  30. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/errors.py +0 -7
  31. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/evaluation_modes.py +0 -25
  32. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/hacks.py +0 -23
  33. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/json_schema_helper.py +0 -384
  34. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/mitm_addons.py +0 -37
  35. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/models.py +0 -595
  36. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/proxy.py +0 -207
  37. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/secret_access.py +0 -47
  38. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/segment_tracking.py +0 -45
  39. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/utils.py +0 -214
  40. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/conftest.py.disabled +0 -751
  41. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/consts.py +0 -4
  42. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/poetry.lock +0 -4480
  43. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/pytest.ini +0 -9
  44. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/__init__.py +0 -1
  45. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_check.py +0 -61
  46. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_discover.py +0 -117
  47. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_read.py +0 -627
  48. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_spec.py +0 -43
  49. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/report.py +0 -542
  50. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/stash_keys.py +0 -38
  51. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/__init__.py +0 -0
  52. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/private_details.html.j2 +0 -305
  53. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/report.html.j2 +0 -515
  54. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/utils.py +0 -187
  55. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/__init__.py +0 -0
  56. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_check.py +0 -61
  57. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_discover.py +0 -217
  58. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_read.py +0 -177
  59. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_spec.py +0 -631
  60. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/README.md +0 -91
  61. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/bin/bundle-schemas.js +0 -48
  62. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/bin/generate-metadata-models.sh +0 -36
  63. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ActorDefinitionResourceRequirements.py +0 -54
  64. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/AirbyteInternal.py +0 -22
  65. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/AllowedHosts.py +0 -18
  66. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorBreakingChanges.py +0 -65
  67. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorBuildOptions.py +0 -15
  68. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorIPCOptions.py +0 -25
  69. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetadataDefinitionV0.json +0 -897
  70. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetadataDefinitionV0.py +0 -478
  71. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetrics.py +0 -24
  72. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorPackageInfo.py +0 -12
  73. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryDestinationDefinition.py +0 -407
  74. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryReleases.py +0 -406
  75. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistrySourceDefinition.py +0 -407
  76. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryV0.py +0 -413
  77. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorReleases.py +0 -98
  78. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorTestSuiteOptions.py +0 -58
  79. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/GeneratedFields.py +0 -62
  80. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/GitInfo.py +0 -31
  81. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/JobType.py +0 -23
  82. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/NormalizationDestinationDefinitionConfig.py +0 -24
  83. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RegistryOverrides.py +0 -111
  84. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ReleaseStage.py +0 -15
  85. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RemoteRegistries.py +0 -23
  86. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ResourceRequirements.py +0 -18
  87. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RolloutConfiguration.py +0 -29
  88. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/Secret.py +0 -34
  89. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SecretStore.py +0 -22
  90. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SourceFileInfo.py +0 -16
  91. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SuggestedStreams.py +0 -18
  92. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SupportLevel.py +0 -15
  93. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/TestConnections.py +0 -14
  94. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/__init__.py +0 -31
  95. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/airbyte-connector-metadata-schema.json +0 -0
  96. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ActorDefinitionResourceRequirements.yaml +0 -30
  97. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/AirbyteInternal.yaml +0 -32
  98. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/AllowedHosts.yaml +0 -13
  99. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorBreakingChanges.yaml +0 -65
  100. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorBuildOptions.yaml +0 -10
  101. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorIPCOptions.yaml +0 -29
  102. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorMetadataDefinitionV0.yaml +0 -172
  103. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorMetrics.yaml +0 -30
  104. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorPackageInfo.yaml +0 -9
  105. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryDestinationDefinition.yaml +0 -90
  106. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryReleases.yaml +0 -35
  107. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistrySourceDefinition.yaml +0 -92
  108. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryV0.yaml +0 -18
  109. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorReleases.yaml +0 -16
  110. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorTestSuiteOptions.yaml +0 -28
  111. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/GeneratedFields.yaml +0 -16
  112. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/GitInfo.yaml +0 -21
  113. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/JobType.yaml +0 -14
  114. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/NormalizationDestinationDefinitionConfig.yaml +0 -21
  115. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RegistryOverrides.yaml +0 -38
  116. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ReleaseStage.yaml +0 -11
  117. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RemoteRegistries.yaml +0 -25
  118. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ResourceRequirements.yaml +0 -16
  119. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RolloutConfiguration.yaml +0 -29
  120. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/Secret.yaml +0 -19
  121. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SecretStore.yaml +0 -16
  122. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SourceFileInfo.yaml +0 -17
  123. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SuggestedStreams.yaml +0 -13
  124. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SupportLevel.yaml +0 -10
  125. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/TestConnections.yaml +0 -17
  126. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/package-lock.json +0 -62
  127. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/package.json +0 -12
  128. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/transform.py +0 -71
  129. {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/WHEEL +0 -0
  130. {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.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]()