recce-nightly 1.2.0.20250506__py3-none-any.whl → 1.26.0.20251124__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.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +27 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +810 -480
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +39 -28
- recce/apis/check_func.py +33 -27
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +29 -23
- recce/artifact.py +119 -51
- recce/cli.py +1299 -323
- recce/config.py +42 -33
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404.html +1 -1
- recce/data/__next.__PAGE__.txt +10 -0
- recce/data/__next._full.txt +23 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +8 -0
- recce/data/__next._tree.txt +5 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_buildManifest.js +11 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_clientMiddlewareManifest.json +1 -0
- recce/data/_next/static/chunks/02b996c7f6a29a06.js +4 -0
- recce/data/_next/static/chunks/19c10d219a6a21ff.js +1 -0
- recce/data/_next/static/chunks/2df9ec28a061971d.js +11 -0
- recce/data/_next/static/chunks/3098c987393bda15.js +1 -0
- recce/data/_next/static/chunks/393dc43e483f717a.css +2 -0
- recce/data/_next/static/chunks/399e8d91a7e45073.js +2 -0
- recce/data/_next/static/chunks/4d0186f631230245.js +1 -0
- recce/data/_next/static/chunks/5794ba9e10a9c060.js +11 -0
- recce/data/_next/static/chunks/715761c929a3f28b.js +110 -0
- recce/data/_next/static/chunks/71f88fcc615bf282.js +1 -0
- recce/data/_next/static/chunks/80d2a95eaf1201ea.js +1 -0
- recce/data/_next/static/chunks/9979c6109bbbee35.js +1 -0
- recce/data/_next/static/chunks/99d638224186c118.js +1 -0
- recce/data/_next/static/chunks/d003eb36240e92f3.js +1 -0
- recce/data/_next/static/chunks/d3167cdfec4fc351.js +1 -0
- recce/data/_next/static/chunks/e124bccf574a3361.css +1 -0
- recce/data/_next/static/chunks/f40141db1bdb46f0.css +6 -0
- recce/data/_next/static/chunks/fcc53a88741a52f9.js +1 -0
- recce/data/_next/static/chunks/turbopack-b1920d28cfb1f28d.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/media/reload-image.7aa931c7.svg +4 -0
- recce/data/_not-found/__next._full.txt +17 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +8 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +3 -0
- recce/data/_not-found.html +1 -0
- recce/data/_not-found.txt +17 -0
- recce/data/auth_callback.html +68 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +1 -27
- recce/data/index.txt +23 -7
- recce/diff.py +6 -12
- recce/event/__init__.py +86 -74
- recce/event/collector.py +33 -22
- recce/event/track.py +49 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/mcp_server.py +716 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +6 -7
- recce/models/run.py +1 -0
- recce/models/types.py +131 -28
- recce/pull_request.py +27 -25
- recce/run.py +165 -121
- recce/server.py +303 -111
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +632 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +188 -143
- recce/tasks/__init__.py +19 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +82 -18
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +152 -86
- recce/tasks/query.py +139 -87
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/valuediff.py +216 -152
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +87 -85
- recce/util/cll.py +274 -219
- recce/util/io.py +22 -17
- recce/util/lineage.py +65 -16
- recce/util/logger.py +1 -1
- recce/util/onboarding_state.py +45 -0
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +322 -72
- recce/util/singleton.py +4 -4
- recce/yaml/__init__.py +7 -10
- recce_cloud/__init__.py +24 -0
- recce_cloud/api/__init__.py +17 -0
- recce_cloud/api/base.py +111 -0
- recce_cloud/api/client.py +150 -0
- recce_cloud/api/exceptions.py +26 -0
- recce_cloud/api/factory.py +63 -0
- recce_cloud/api/github.py +76 -0
- recce_cloud/api/gitlab.py +82 -0
- recce_cloud/artifact.py +57 -0
- recce_cloud/ci_providers/__init__.py +9 -0
- recce_cloud/ci_providers/base.py +82 -0
- recce_cloud/ci_providers/detector.py +147 -0
- recce_cloud/ci_providers/github_actions.py +136 -0
- recce_cloud/ci_providers/gitlab_ci.py +130 -0
- recce_cloud/cli.py +245 -0
- recce_cloud/upload.py +214 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/METADATA +68 -37
- recce_nightly-1.26.0.20251124.dist-info/RECORD +180 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/WHEEL +1 -1
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/top_level.txt +1 -0
- tests/adapter/dbt_adapter/conftest.py +9 -5
- tests/adapter/dbt_adapter/dbt_test_helper.py +37 -22
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -15
- tests/adapter/dbt_adapter/test_dbt_cll.py +656 -41
- tests/adapter/dbt_adapter/test_selector.py +22 -21
- tests/recce_cloud/__init__.py +0 -0
- tests/recce_cloud/test_ci_providers.py +351 -0
- tests/recce_cloud/test_cli.py +372 -0
- tests/recce_cloud/test_client.py +273 -0
- tests/recce_cloud/test_platform_clients.py +333 -0
- tests/tasks/conftest.py +1 -1
- tests/tasks/test_histogram.py +58 -66
- tests/tasks/test_lineage.py +36 -23
- tests/tasks/test_preset_checks.py +45 -31
- tests/tasks/test_profile.py +339 -15
- tests/tasks/test_query.py +46 -46
- tests/tasks/test_row_count.py +65 -46
- tests/tasks/test_schema.py +65 -42
- tests/tasks/test_top_k.py +22 -18
- tests/tasks/test_valuediff.py +43 -32
- tests/test_cli.py +174 -60
- tests/test_cli_mcp_optional.py +45 -0
- tests/test_cloud_listing_cli.py +324 -0
- tests/test_config.py +7 -9
- tests/test_connect_to_cloud.py +82 -0
- tests/test_core.py +151 -4
- tests/test_dbt.py +7 -7
- tests/test_mcp_server.py +332 -0
- tests/test_pull_request.py +1 -1
- tests/test_server.py +25 -19
- tests/test_summary.py +29 -17
- recce/data/_next/static/Kcbs3GEIyH2LxgLYat0es/_buildManifest.js +0 -1
- recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
- recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
- recce/data/_next/static/chunks/368-7587b306577df275.js +0 -65
- recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
- recce/data/_next/static/chunks/3a92ee20-3b5d922d4157af5e.js +0 -1
- recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
- recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/6ef81909-694dc38134099299.js +0 -1
- recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
- recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
- recce/data/_next/static/chunks/8d700b6a-f0b1f6b9e0d97ce2.js +0 -1
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
- recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-cee661090afbd6aa.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
- recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
- recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
- recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
- recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
- recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
- recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
- recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
- recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/state.py +0 -753
- recce_nightly-1.2.0.20250506.dist-info/RECORD +0 -142
- tests/test_state.py +0 -123
- /recce/data/_next/static/{Kcbs3GEIyH2LxgLYat0es → 52aV_JrNUZU6dMFgvTQEO}/_ssgManifest.js +0 -0
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/licenses/LICENSE +0 -0
recce/run.py
CHANGED
|
@@ -2,14 +2,18 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
import time
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import Dict, List, Tuple
|
|
6
6
|
|
|
7
7
|
from deepdiff import DeepDiff
|
|
8
8
|
from rich import box
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
from rich.table import Table
|
|
11
11
|
|
|
12
|
-
from recce.apis.check_func import
|
|
12
|
+
from recce.apis.check_func import (
|
|
13
|
+
create_check_from_run,
|
|
14
|
+
create_check_without_run,
|
|
15
|
+
purge_preset_checks,
|
|
16
|
+
)
|
|
13
17
|
from recce.apis.run_func import submit_run
|
|
14
18
|
from recce.config import RecceConfig
|
|
15
19
|
from recce.core import default_context
|
|
@@ -19,13 +23,13 @@ from recce.summary import generate_markdown_summary
|
|
|
19
23
|
|
|
20
24
|
def check_github_ci_env(**kwargs):
|
|
21
25
|
"""Check if the environment is GitHub CI"""
|
|
22
|
-
if
|
|
26
|
+
if "GITHUB_ACTIONS" not in os.environ:
|
|
23
27
|
return False, None
|
|
24
28
|
|
|
25
29
|
# Get the PR number
|
|
26
|
-
github_server_url = os.environ.get(
|
|
27
|
-
github_repository = os.environ.get(
|
|
28
|
-
github_ref_name = os.environ.get(
|
|
30
|
+
github_server_url = os.environ.get("GITHUB_SERVER_URL", "https://github.com")
|
|
31
|
+
github_repository = os.environ.get("GITHUB_REPOSITORY", "")
|
|
32
|
+
github_ref_name = os.environ.get("GITHUB_REF_NAME", "")
|
|
29
33
|
github_pull_request_url = f"{github_server_url}/{github_repository}/pull/{github_ref_name}"
|
|
30
34
|
|
|
31
35
|
return True, github_pull_request_url
|
|
@@ -33,21 +37,22 @@ def check_github_ci_env(**kwargs):
|
|
|
33
37
|
|
|
34
38
|
def load_preset_checks(checks: list):
|
|
35
39
|
console = Console()
|
|
36
|
-
table = Table(title=
|
|
37
|
-
table.add_column(
|
|
38
|
-
table.add_column(
|
|
39
|
-
table.add_column(
|
|
40
|
+
table = Table(title="Recce Preset Checks", box=box.HORIZONTALS, title_style="bold dark_orange3")
|
|
41
|
+
table.add_column("Name")
|
|
42
|
+
table.add_column("Type")
|
|
43
|
+
table.add_column("Description")
|
|
40
44
|
for check in checks:
|
|
41
45
|
is_check = False
|
|
42
|
-
name = check.get(
|
|
43
|
-
description = check.get(
|
|
44
|
-
check_type = check.get(
|
|
45
|
-
check_params = check.get(
|
|
46
|
-
check_options = check.get(
|
|
47
|
-
|
|
48
|
-
create_check_without_run(
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
name = check.get("name")
|
|
47
|
+
description = check.get("description", "")
|
|
48
|
+
check_type = check.get("type")
|
|
49
|
+
check_params = check.get("params", {})
|
|
50
|
+
check_options = check.get("view_options", {})
|
|
51
|
+
|
|
52
|
+
create_check_without_run(
|
|
53
|
+
name, description, check_type, check_params, check_options, is_preset=True, is_checked=is_check
|
|
54
|
+
)
|
|
55
|
+
table.add_row(name, check_type.replace("_", " ").title(), description.strip())
|
|
51
56
|
console.print(table)
|
|
52
57
|
|
|
53
58
|
|
|
@@ -55,28 +60,28 @@ def schema_diff_should_be_approved(check_params: dict) -> bool:
|
|
|
55
60
|
try:
|
|
56
61
|
context = default_context()
|
|
57
62
|
|
|
58
|
-
if
|
|
63
|
+
if "node_id" in check_params:
|
|
59
64
|
# If the node_id is provided, then use it
|
|
60
|
-
if isinstance(check_params[
|
|
61
|
-
selected_node_ids = [check_params[
|
|
65
|
+
if isinstance(check_params["node_id"], str):
|
|
66
|
+
selected_node_ids = [check_params["node_id"]]
|
|
62
67
|
else:
|
|
63
|
-
selected_node_ids = check_params.get(
|
|
68
|
+
selected_node_ids = check_params.get("node_id", [])
|
|
64
69
|
else:
|
|
65
70
|
# Otherwise, select the nodes based on the select/exclude/packages/view_mode
|
|
66
71
|
selected_node_ids = context.adapter.select_nodes(
|
|
67
|
-
select=check_params.get(
|
|
68
|
-
exclude=check_params.get(
|
|
69
|
-
packages=check_params.get(
|
|
70
|
-
view_mode=check_params.get(
|
|
72
|
+
select=check_params.get("select"),
|
|
73
|
+
exclude=check_params.get("exclude"),
|
|
74
|
+
packages=check_params.get("packages"),
|
|
75
|
+
view_mode=check_params.get("view_mode"),
|
|
71
76
|
)
|
|
72
77
|
|
|
73
|
-
selected_node_ids = [node for node in selected_node_ids if not node.startswith(
|
|
78
|
+
selected_node_ids = [node for node in selected_node_ids if not node.startswith("test.")]
|
|
74
79
|
|
|
75
80
|
def _get_selected_node_columns_from_lineage(lineage, node_ids: list[str]):
|
|
76
81
|
nodes = {}
|
|
77
|
-
for node_id, node in lineage.get(
|
|
82
|
+
for node_id, node in lineage.get("nodes", {}).items():
|
|
78
83
|
if node_id in node_ids:
|
|
79
|
-
nodes[node_id] = node.get(
|
|
84
|
+
nodes[node_id] = node.get("columns", {})
|
|
80
85
|
return nodes
|
|
81
86
|
|
|
82
87
|
base_nodes = _get_selected_node_columns_from_lineage(context.get_lineage(base=True), selected_node_ids)
|
|
@@ -100,25 +105,25 @@ def run_should_be_approved(run):
|
|
|
100
105
|
return False
|
|
101
106
|
# If the row count are exactly the same, then the check should be approved
|
|
102
107
|
for column, row_count_result in run.result.items():
|
|
103
|
-
if row_count_result[
|
|
108
|
+
if row_count_result["base"] != row_count_result["curr"]:
|
|
104
109
|
return False
|
|
105
110
|
return True
|
|
106
111
|
return False
|
|
107
112
|
|
|
108
113
|
|
|
109
|
-
async def execute_preset_checks(preset_checks:
|
|
114
|
+
async def execute_preset_checks(preset_checks: List, is_skip_query: bool) -> Tuple[int, List[Dict]]:
|
|
110
115
|
"""
|
|
111
116
|
Execute the preset checks
|
|
112
117
|
"""
|
|
113
118
|
console = Console()
|
|
114
119
|
rc = 0
|
|
115
120
|
failed_checks = []
|
|
116
|
-
table = Table(title=
|
|
117
|
-
table.add_column(
|
|
118
|
-
table.add_column(
|
|
119
|
-
table.add_column(
|
|
120
|
-
table.add_column(
|
|
121
|
-
table.add_column(
|
|
121
|
+
table = Table(title="Recce Preset Checks", box=box.HORIZONTALS, title_style="bold dark_orange3")
|
|
122
|
+
table.add_column("Status")
|
|
123
|
+
table.add_column("Name")
|
|
124
|
+
table.add_column("Type")
|
|
125
|
+
table.add_column("Execution Time")
|
|
126
|
+
table.add_column("Failed Reason")
|
|
122
127
|
|
|
123
128
|
# Purge the existing preset checks before running the new ones
|
|
124
129
|
purge_preset_checks()
|
|
@@ -126,11 +131,11 @@ async def execute_preset_checks(preset_checks: list) -> (int, List[dict]):
|
|
|
126
131
|
# Execute the preset checks
|
|
127
132
|
for check in preset_checks:
|
|
128
133
|
run = None
|
|
129
|
-
check_name = check.get(
|
|
130
|
-
check_type = check.get(
|
|
131
|
-
check_description = check.get(
|
|
132
|
-
check_params = check.get(
|
|
133
|
-
check_options = check.get(
|
|
134
|
+
check_name = check.get("name")
|
|
135
|
+
check_type = check.get("type")
|
|
136
|
+
check_description = check.get("description", "")
|
|
137
|
+
check_params = check.get("params") if check.get("params") else {}
|
|
138
|
+
check_options = check.get("view_options", {})
|
|
134
139
|
|
|
135
140
|
try:
|
|
136
141
|
# verify the check
|
|
@@ -138,59 +143,82 @@ async def execute_preset_checks(preset_checks: list) -> (int, List[dict]):
|
|
|
138
143
|
raise ValueError(f"Invalid check type: {check_type}")
|
|
139
144
|
|
|
140
145
|
start = time.time()
|
|
141
|
-
if check_type in [
|
|
142
|
-
is_check = schema_diff_should_be_approved(check_params) if check_type ==
|
|
143
|
-
create_check_without_run(
|
|
144
|
-
|
|
146
|
+
if check_type in ["schema_diff", "lineage_diff"]:
|
|
147
|
+
is_check = schema_diff_should_be_approved(check_params) if check_type == "schema_diff" else False
|
|
148
|
+
create_check_without_run(
|
|
149
|
+
check_name,
|
|
150
|
+
check_description,
|
|
151
|
+
check_type,
|
|
152
|
+
check_params,
|
|
153
|
+
check_options,
|
|
154
|
+
is_preset=True,
|
|
155
|
+
is_checked=is_check,
|
|
156
|
+
)
|
|
145
157
|
else:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
if not is_skip_query:
|
|
159
|
+
run, future = submit_run(check_type, params=check_params)
|
|
160
|
+
await future
|
|
161
|
+
is_check = run_should_be_approved(run)
|
|
162
|
+
create_check_from_run(
|
|
163
|
+
run.run_id, check_name, check_description, check_options, is_preset=True, is_checked=is_check
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
create_check_without_run(
|
|
167
|
+
check_name, check_description, check_type, check_params, check_options, is_preset=True
|
|
168
|
+
)
|
|
169
|
+
continue
|
|
151
170
|
|
|
152
171
|
end = time.time()
|
|
153
|
-
table.add_row(
|
|
154
|
-
|
|
172
|
+
table.add_row(
|
|
173
|
+
"[[green]Success[/green]]",
|
|
174
|
+
check_name,
|
|
175
|
+
check_type.replace("_", " ").title(),
|
|
176
|
+
f"{end - start:.2f} seconds",
|
|
177
|
+
"N/A",
|
|
178
|
+
)
|
|
155
179
|
except Exception as e:
|
|
156
180
|
rc = 1
|
|
157
181
|
if run is None:
|
|
158
|
-
table.add_row(
|
|
159
|
-
failed_checks.append(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
182
|
+
table.add_row("[[red]Error[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", str(e))
|
|
183
|
+
failed_checks.append(
|
|
184
|
+
{
|
|
185
|
+
"check_name": check_name,
|
|
186
|
+
"check_type": check_type,
|
|
187
|
+
"check_description": check_description,
|
|
188
|
+
"failed_type": "error",
|
|
189
|
+
"failed_reason": str(e),
|
|
190
|
+
}
|
|
191
|
+
)
|
|
166
192
|
else:
|
|
167
193
|
create_check_from_run(run.run_id, check_name, check_description, check_options, is_preset=True)
|
|
168
|
-
table.add_row(
|
|
169
|
-
failed_checks.append(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
194
|
+
table.add_row("[[red]Failed[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", run.error)
|
|
195
|
+
failed_checks.append(
|
|
196
|
+
{
|
|
197
|
+
"check_name": check_name,
|
|
198
|
+
"check_type": check_type,
|
|
199
|
+
"check_description": "N/A",
|
|
200
|
+
"failed_type": "failed",
|
|
201
|
+
"failed_reason": run.error,
|
|
202
|
+
}
|
|
203
|
+
)
|
|
176
204
|
|
|
177
205
|
console.print(table)
|
|
178
206
|
return rc, failed_checks
|
|
179
207
|
|
|
180
208
|
|
|
181
|
-
async def execute_state_checks(checks:
|
|
209
|
+
async def execute_state_checks(checks: List, is_skip_query: bool) -> Tuple[int, List[Dict]]:
|
|
182
210
|
"""
|
|
183
211
|
Execute the checks from loaded state
|
|
184
212
|
"""
|
|
185
213
|
console = Console()
|
|
186
214
|
rc = 0
|
|
187
215
|
failed_checks = []
|
|
188
|
-
table = Table(title=
|
|
189
|
-
table.add_column(
|
|
190
|
-
table.add_column(
|
|
191
|
-
table.add_column(
|
|
192
|
-
table.add_column(
|
|
193
|
-
table.add_column(
|
|
216
|
+
table = Table(title="Recce Checks", box=box.HORIZONTALS, title_style="bold dark_orange3")
|
|
217
|
+
table.add_column("Status")
|
|
218
|
+
table.add_column("Name")
|
|
219
|
+
table.add_column("Type")
|
|
220
|
+
table.add_column("Execution Time")
|
|
221
|
+
table.add_column("Failed Reason")
|
|
194
222
|
|
|
195
223
|
# Execute loaded checks
|
|
196
224
|
for check in checks:
|
|
@@ -210,33 +238,42 @@ async def execute_state_checks(checks: list) -> (int, List[dict]):
|
|
|
210
238
|
raise ValueError(f"Invalid check type: {check_type}")
|
|
211
239
|
|
|
212
240
|
start = time.time()
|
|
213
|
-
if check_type not in [
|
|
241
|
+
if check_type not in ["schema_diff", "lineage_diff"] and not is_skip_query:
|
|
214
242
|
run, future = submit_run(check_type, params=check_params, check_id=check_id)
|
|
215
243
|
await future
|
|
216
244
|
|
|
217
245
|
end = time.time()
|
|
218
|
-
table.add_row(
|
|
219
|
-
|
|
246
|
+
table.add_row(
|
|
247
|
+
"[[green]Success[/green]]",
|
|
248
|
+
check_name,
|
|
249
|
+
check_type.replace("_", " ").title(),
|
|
250
|
+
f"{end - start:.2f} seconds",
|
|
251
|
+
"N/A",
|
|
252
|
+
)
|
|
220
253
|
except Exception as e:
|
|
221
254
|
rc = 1
|
|
222
255
|
if run is None:
|
|
223
|
-
table.add_row(
|
|
224
|
-
failed_checks.append(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
256
|
+
table.add_row("[[red]Error[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", str(e))
|
|
257
|
+
failed_checks.append(
|
|
258
|
+
{
|
|
259
|
+
"check_name": check_name,
|
|
260
|
+
"check_type": check_type,
|
|
261
|
+
"check_description": check_description,
|
|
262
|
+
"failed_type": "error",
|
|
263
|
+
"failed_reason": str(e),
|
|
264
|
+
}
|
|
265
|
+
)
|
|
231
266
|
else:
|
|
232
|
-
table.add_row(
|
|
233
|
-
failed_checks.append(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
267
|
+
table.add_row("[[red]Failed[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", run.error)
|
|
268
|
+
failed_checks.append(
|
|
269
|
+
{
|
|
270
|
+
"check_name": check_name,
|
|
271
|
+
"check_type": check_type,
|
|
272
|
+
"check_description": "N/A",
|
|
273
|
+
"failed_type": "failed",
|
|
274
|
+
"failed_reason": run.error,
|
|
275
|
+
}
|
|
276
|
+
)
|
|
240
277
|
|
|
241
278
|
console.print(table)
|
|
242
279
|
return rc, failed_checks
|
|
@@ -244,24 +281,27 @@ async def execute_state_checks(checks: list) -> (int, List[dict]):
|
|
|
244
281
|
|
|
245
282
|
def process_failed_checks(failed_checks: List[dict], error_log=None):
|
|
246
283
|
from py_markdown_table.markdown_table import markdown_table
|
|
284
|
+
|
|
247
285
|
failed_check_table = []
|
|
248
286
|
for check in failed_checks:
|
|
249
|
-
name = check.get(
|
|
250
|
-
check_type = check.get(
|
|
251
|
-
failed_type = check.get(
|
|
252
|
-
failed_reason = check.get(
|
|
253
|
-
failed_check_table.append(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
287
|
+
name = check.get("check_name")
|
|
288
|
+
check_type = check.get("check_type")
|
|
289
|
+
failed_type = check.get("failed_type")
|
|
290
|
+
failed_reason = check.get("failed_reason")
|
|
291
|
+
failed_check_table.append(
|
|
292
|
+
{
|
|
293
|
+
"Name": name,
|
|
294
|
+
"Type": check_type,
|
|
295
|
+
"Kind of Failed": failed_type,
|
|
296
|
+
"Failed Reason": failed_reason.replace("\n", " "),
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
content = "# Recce Runc Failed Checks\n"
|
|
301
|
+
content += markdown_table(failed_check_table).set_params(quote=False, row_sep="markdown").get_markdown()
|
|
262
302
|
|
|
263
303
|
if error_log:
|
|
264
|
-
with open(error_log,
|
|
304
|
+
with open(error_log, "w", encoding="utf-8") as f:
|
|
265
305
|
f.write(content)
|
|
266
306
|
print(f"The failed checks are stored at '{error_log}'")
|
|
267
307
|
else:
|
|
@@ -271,49 +311,53 @@ def process_failed_checks(failed_checks: List[dict], error_log=None):
|
|
|
271
311
|
async def cli_run(output_state_file: str, **kwargs):
|
|
272
312
|
"""The main function of 'recce run' command. It will execute the default runs and store the state."""
|
|
273
313
|
console = Console()
|
|
274
|
-
error_log = kwargs.get(
|
|
275
|
-
if kwargs.get(
|
|
314
|
+
error_log = kwargs.get("error_log")
|
|
315
|
+
if kwargs.get("sqlmesh", False):
|
|
276
316
|
console.print("[[red]Error[/red]] SQLMesh adapter is not supported.")
|
|
277
317
|
sys.exit(1)
|
|
278
318
|
|
|
279
319
|
from recce.core import load_context
|
|
320
|
+
|
|
280
321
|
ctx = load_context(**kwargs)
|
|
281
322
|
|
|
282
|
-
is_skip_query = kwargs.get(
|
|
323
|
+
is_skip_query = kwargs.get("skip_query", False)
|
|
324
|
+
is_skip_check = kwargs.get("skip_check", False)
|
|
283
325
|
|
|
284
326
|
# Prepare the artifact by collecting the lineage
|
|
285
327
|
console.rule("DBT Artifacts")
|
|
286
328
|
from recce.adapter.dbt_adapter import DbtAdapter
|
|
329
|
+
|
|
287
330
|
dbt_adaptor: DbtAdapter = ctx.adapter
|
|
288
331
|
dbt_adaptor.print_lineage_info()
|
|
289
332
|
|
|
290
333
|
# Execute the preset checks
|
|
291
334
|
rc = 0
|
|
292
335
|
if ctx.state_loader.state is None:
|
|
293
|
-
preset_checks = RecceConfig().get(
|
|
294
|
-
if
|
|
336
|
+
preset_checks = RecceConfig().get("checks")
|
|
337
|
+
if is_skip_check or preset_checks is None or len(preset_checks) == 0:
|
|
295
338
|
# Skip the preset checks
|
|
296
339
|
pass
|
|
297
340
|
else:
|
|
298
341
|
console.rule("Preset checks")
|
|
299
|
-
_, failed_checks = await execute_preset_checks(preset_checks)
|
|
342
|
+
_, failed_checks = await execute_preset_checks(preset_checks, is_skip_query)
|
|
300
343
|
if failed_checks:
|
|
301
344
|
console.print("[[yellow]Warning[/yellow]] Preset checks failed. Please see the failed reason.")
|
|
302
345
|
process_failed_checks(failed_checks, error_log)
|
|
303
346
|
else:
|
|
304
347
|
state_checks = ctx.state_loader.state.checks
|
|
305
|
-
if
|
|
348
|
+
if is_skip_check or state_checks is None or len(state_checks) == 0:
|
|
306
349
|
# Skip the checks in the state
|
|
307
350
|
pass
|
|
308
351
|
else:
|
|
309
352
|
console.rule("Checks")
|
|
310
|
-
_, failed_checks = await execute_state_checks(state_checks)
|
|
353
|
+
_, failed_checks = await execute_state_checks(state_checks, is_skip_query)
|
|
311
354
|
if failed_checks:
|
|
312
355
|
console.print("[[yellow]Warning[/yellow]] Checks failed. Please see the failed reason.")
|
|
313
356
|
process_failed_checks(failed_checks, error_log)
|
|
314
357
|
|
|
315
358
|
from recce.event import log_load_state
|
|
316
|
-
|
|
359
|
+
|
|
360
|
+
log_load_state(command="run")
|
|
317
361
|
|
|
318
362
|
# Export the state
|
|
319
363
|
console.rule("Export state")
|
|
@@ -321,12 +365,12 @@ async def cli_run(output_state_file: str, **kwargs):
|
|
|
321
365
|
msg = ctx.state_loader.export(ctx.export_state())
|
|
322
366
|
console.print(msg)
|
|
323
367
|
|
|
324
|
-
summary_path = kwargs.get(
|
|
368
|
+
summary_path = kwargs.get("summary")
|
|
325
369
|
if summary_path:
|
|
326
370
|
dirs = os.path.dirname(summary_path)
|
|
327
371
|
if dirs:
|
|
328
372
|
os.makedirs(dirs, exist_ok=True)
|
|
329
|
-
with open(summary_path,
|
|
373
|
+
with open(summary_path, "w", encoding="utf-8") as f:
|
|
330
374
|
f.write(generate_markdown_summary(ctx))
|
|
331
375
|
console.print(f"The summary is stored at '{summary_path}'")
|
|
332
376
|
|