recce-nightly 0.62.0.20250417__py3-none-any.whl → 1.30.0.20251221__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 +845 -461
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +59 -42
- recce/apis/check_events_api.py +353 -0
- recce/apis/check_func.py +41 -35
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +64 -25
- recce/artifact.py +119 -51
- recce/cli.py +1301 -324
- recce/config.py +43 -34
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404/index.html +2 -0
- recce/data/404.html +2 -1
- recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
- recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
- recce/data/__next.__PAGE__.txt +6 -0
- recce/data/__next._full.txt +32 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +14 -0
- recce/data/__next._tree.txt +8 -0
- recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
- recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
- recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
- recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
- recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
- recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
- recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
- recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
- recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
- recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
- recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
- recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
- recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
- recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
- recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
- recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
- recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
- recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
- recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
- recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
- recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
- recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
- recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
- recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
- recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
- recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
- recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
- recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
- recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
- recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
- recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
- recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
- recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
- recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
- recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
- recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
- recce/data/_next/static/chunks/turbopack-1fad664f62979b93.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/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
- recce/data/_not-found/__next._full.txt +24 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +13 -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 +6 -0
- recce/data/_not-found/index.html +2 -0
- recce/data/_not-found/index.txt +24 -0
- recce/data/auth_callback.html +68 -0
- recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/checks/__next._full.txt +39 -0
- recce/data/checks/__next._head.txt +8 -0
- recce/data/checks/__next._index.txt +14 -0
- recce/data/checks/__next._tree.txt +8 -0
- recce/data/checks/__next.checks.__PAGE__.txt +10 -0
- recce/data/checks/__next.checks.txt +4 -0
- recce/data/checks/index.html +2 -0
- recce/data/checks/index.txt +39 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +2 -27
- recce/data/index.txt +32 -7
- recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/lineage/__next._full.txt +39 -0
- recce/data/lineage/__next._head.txt +8 -0
- recce/data/lineage/__next._index.txt +14 -0
- recce/data/lineage/__next._tree.txt +8 -0
- recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
- recce/data/lineage/__next.lineage.txt +4 -0
- recce/data/lineage/index.html +2 -0
- recce/data/lineage/index.txt +39 -0
- recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/query/__next._full.txt +37 -0
- recce/data/query/__next._head.txt +8 -0
- recce/data/query/__next._index.txt +14 -0
- recce/data/query/__next._tree.txt +8 -0
- recce/data/query/__next.query.__PAGE__.txt +9 -0
- recce/data/query/__next.query.txt +4 -0
- recce/data/query/index.html +2 -0
- recce/data/query/index.txt +37 -0
- recce/diff.py +6 -12
- recce/event/CONFIG.bak +1 -0
- 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 +725 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +438 -21
- recce/models/run.py +1 -0
- recce/models/types.py +134 -28
- recce/pull_request.py +27 -25
- recce/run.py +179 -122
- recce/server.py +394 -104
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +644 -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 +196 -149
- 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 +180 -89
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/utils.py +147 -0
- recce/tasks/valuediff.py +247 -155
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +105 -100
- recce/util/cll.py +274 -219
- recce/util/cloud/__init__.py +15 -0
- recce/util/cloud/base.py +115 -0
- recce/util/cloud/check_events.py +190 -0
- recce/util/cloud/checks.py +242 -0
- 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 +347 -72
- recce/util/singleton.py +4 -4
- recce/util/startup_perf.py +121 -0
- recce/yaml/__init__.py +7 -10
- recce_nightly-1.30.0.20251221.dist-info/METADATA +195 -0
- recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
- 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/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.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/500-e51c92a025a51234.js +0 -65
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.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/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-9adc25782272ed2e.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/data/_next/static/qiyFlux77VkhxiceAJe_F/_buildManifest.js +0 -1
- recce/state.py +0 -753
- recce_nightly-0.62.0.20250417.dist-info/METADATA +0 -311
- recce_nightly-0.62.0.20250417.dist-info/RECORD +0 -139
- recce_nightly-0.62.0.20250417.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/conftest.py +0 -13
- tests/adapter/dbt_adapter/dbt_test_helper.py +0 -283
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -40
- tests/adapter/dbt_adapter/test_dbt_cll.py +0 -102
- tests/adapter/dbt_adapter/test_selector.py +0 -177
- tests/tasks/__init__.py +0 -0
- tests/tasks/conftest.py +0 -4
- tests/tasks/test_histogram.py +0 -137
- tests/tasks/test_lineage.py +0 -42
- tests/tasks/test_preset_checks.py +0 -50
- tests/tasks/test_profile.py +0 -73
- tests/tasks/test_query.py +0 -151
- tests/tasks/test_row_count.py +0 -116
- tests/tasks/test_schema.py +0 -99
- tests/tasks/test_top_k.py +0 -73
- tests/tasks/test_valuediff.py +0 -74
- tests/test_cli.py +0 -122
- tests/test_config.py +0 -45
- tests/test_core.py +0 -27
- tests/test_dbt.py +0 -36
- tests/test_pull_request.py +0 -130
- tests/test_server.py +0 -98
- tests/test_state.py +0 -123
- tests/test_summary.py +0 -57
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- /recce/data/_next/static/{qiyFlux77VkhxiceAJe_F → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE +0 -0
recce/run.py
CHANGED
|
@@ -2,30 +2,35 @@ 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
|
|
20
|
+
from recce.models import CheckDAO
|
|
16
21
|
from recce.models.types import RunType
|
|
17
22
|
from recce.summary import generate_markdown_summary
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
def check_github_ci_env(**kwargs):
|
|
21
26
|
"""Check if the environment is GitHub CI"""
|
|
22
|
-
if
|
|
27
|
+
if "GITHUB_ACTIONS" not in os.environ:
|
|
23
28
|
return False, None
|
|
24
29
|
|
|
25
30
|
# Get the PR number
|
|
26
|
-
github_server_url = os.environ.get(
|
|
27
|
-
github_repository = os.environ.get(
|
|
28
|
-
github_ref_name = os.environ.get(
|
|
31
|
+
github_server_url = os.environ.get("GITHUB_SERVER_URL", "https://github.com")
|
|
32
|
+
github_repository = os.environ.get("GITHUB_REPOSITORY", "")
|
|
33
|
+
github_ref_name = os.environ.get("GITHUB_REF_NAME", "")
|
|
29
34
|
github_pull_request_url = f"{github_server_url}/{github_repository}/pull/{github_ref_name}"
|
|
30
35
|
|
|
31
36
|
return True, github_pull_request_url
|
|
@@ -33,21 +38,22 @@ def check_github_ci_env(**kwargs):
|
|
|
33
38
|
|
|
34
39
|
def load_preset_checks(checks: list):
|
|
35
40
|
console = Console()
|
|
36
|
-
table = Table(title=
|
|
37
|
-
table.add_column(
|
|
38
|
-
table.add_column(
|
|
39
|
-
table.add_column(
|
|
41
|
+
table = Table(title="Recce Preset Checks", box=box.HORIZONTALS, title_style="bold dark_orange3")
|
|
42
|
+
table.add_column("Name")
|
|
43
|
+
table.add_column("Type")
|
|
44
|
+
table.add_column("Description")
|
|
40
45
|
for check in checks:
|
|
41
46
|
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
|
-
|
|
47
|
+
name = check.get("name")
|
|
48
|
+
description = check.get("description", "")
|
|
49
|
+
check_type = check.get("type")
|
|
50
|
+
check_params = check.get("params", {})
|
|
51
|
+
check_options = check.get("view_options", {})
|
|
52
|
+
|
|
53
|
+
create_check_without_run(
|
|
54
|
+
name, description, check_type, check_params, check_options, is_preset=True, is_checked=is_check
|
|
55
|
+
)
|
|
56
|
+
table.add_row(name, check_type.replace("_", " ").title(), description.strip())
|
|
51
57
|
console.print(table)
|
|
52
58
|
|
|
53
59
|
|
|
@@ -55,28 +61,28 @@ def schema_diff_should_be_approved(check_params: dict) -> bool:
|
|
|
55
61
|
try:
|
|
56
62
|
context = default_context()
|
|
57
63
|
|
|
58
|
-
if
|
|
64
|
+
if "node_id" in check_params:
|
|
59
65
|
# If the node_id is provided, then use it
|
|
60
|
-
if isinstance(check_params[
|
|
61
|
-
selected_node_ids = [check_params[
|
|
66
|
+
if isinstance(check_params["node_id"], str):
|
|
67
|
+
selected_node_ids = [check_params["node_id"]]
|
|
62
68
|
else:
|
|
63
|
-
selected_node_ids = check_params.get(
|
|
69
|
+
selected_node_ids = check_params.get("node_id", [])
|
|
64
70
|
else:
|
|
65
71
|
# Otherwise, select the nodes based on the select/exclude/packages/view_mode
|
|
66
72
|
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(
|
|
73
|
+
select=check_params.get("select"),
|
|
74
|
+
exclude=check_params.get("exclude"),
|
|
75
|
+
packages=check_params.get("packages"),
|
|
76
|
+
view_mode=check_params.get("view_mode"),
|
|
71
77
|
)
|
|
72
78
|
|
|
73
|
-
selected_node_ids = [node for node in selected_node_ids if not node.startswith(
|
|
79
|
+
selected_node_ids = [node for node in selected_node_ids if not node.startswith("test.")]
|
|
74
80
|
|
|
75
81
|
def _get_selected_node_columns_from_lineage(lineage, node_ids: list[str]):
|
|
76
82
|
nodes = {}
|
|
77
|
-
for node_id, node in lineage.get(
|
|
83
|
+
for node_id, node in lineage.get("nodes", {}).items():
|
|
78
84
|
if node_id in node_ids:
|
|
79
|
-
nodes[node_id] = node.get(
|
|
85
|
+
nodes[node_id] = node.get("columns", {})
|
|
80
86
|
return nodes
|
|
81
87
|
|
|
82
88
|
base_nodes = _get_selected_node_columns_from_lineage(context.get_lineage(base=True), selected_node_ids)
|
|
@@ -100,25 +106,25 @@ def run_should_be_approved(run):
|
|
|
100
106
|
return False
|
|
101
107
|
# If the row count are exactly the same, then the check should be approved
|
|
102
108
|
for column, row_count_result in run.result.items():
|
|
103
|
-
if row_count_result[
|
|
109
|
+
if row_count_result["base"] != row_count_result["curr"]:
|
|
104
110
|
return False
|
|
105
111
|
return True
|
|
106
112
|
return False
|
|
107
113
|
|
|
108
114
|
|
|
109
|
-
async def execute_preset_checks(preset_checks:
|
|
115
|
+
async def execute_preset_checks(preset_checks: List, is_skip_query: bool) -> Tuple[int, List[Dict]]:
|
|
110
116
|
"""
|
|
111
117
|
Execute the preset checks
|
|
112
118
|
"""
|
|
113
119
|
console = Console()
|
|
114
120
|
rc = 0
|
|
115
121
|
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(
|
|
122
|
+
table = Table(title="Recce Preset Checks", box=box.HORIZONTALS, title_style="bold dark_orange3")
|
|
123
|
+
table.add_column("Status")
|
|
124
|
+
table.add_column("Name")
|
|
125
|
+
table.add_column("Type")
|
|
126
|
+
table.add_column("Execution Time")
|
|
127
|
+
table.add_column("Failed Reason")
|
|
122
128
|
|
|
123
129
|
# Purge the existing preset checks before running the new ones
|
|
124
130
|
purge_preset_checks()
|
|
@@ -126,11 +132,11 @@ async def execute_preset_checks(preset_checks: list) -> (int, List[dict]):
|
|
|
126
132
|
# Execute the preset checks
|
|
127
133
|
for check in preset_checks:
|
|
128
134
|
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(
|
|
135
|
+
check_name = check.get("name")
|
|
136
|
+
check_type = check.get("type")
|
|
137
|
+
check_description = check.get("description", "")
|
|
138
|
+
check_params = check.get("params") if check.get("params") else {}
|
|
139
|
+
check_options = check.get("view_options", {})
|
|
134
140
|
|
|
135
141
|
try:
|
|
136
142
|
# verify the check
|
|
@@ -138,59 +144,82 @@ async def execute_preset_checks(preset_checks: list) -> (int, List[dict]):
|
|
|
138
144
|
raise ValueError(f"Invalid check type: {check_type}")
|
|
139
145
|
|
|
140
146
|
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
|
-
|
|
147
|
+
if check_type in ["schema_diff", "lineage_diff"]:
|
|
148
|
+
is_check = schema_diff_should_be_approved(check_params) if check_type == "schema_diff" else False
|
|
149
|
+
create_check_without_run(
|
|
150
|
+
check_name,
|
|
151
|
+
check_description,
|
|
152
|
+
check_type,
|
|
153
|
+
check_params,
|
|
154
|
+
check_options,
|
|
155
|
+
is_preset=True,
|
|
156
|
+
is_checked=is_check,
|
|
157
|
+
)
|
|
145
158
|
else:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
if not is_skip_query:
|
|
160
|
+
run, future = submit_run(check_type, params=check_params)
|
|
161
|
+
await future
|
|
162
|
+
is_check = run_should_be_approved(run)
|
|
163
|
+
create_check_from_run(
|
|
164
|
+
run.run_id, check_name, check_description, check_options, is_preset=True, is_checked=is_check
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
create_check_without_run(
|
|
168
|
+
check_name, check_description, check_type, check_params, check_options, is_preset=True
|
|
169
|
+
)
|
|
170
|
+
continue
|
|
151
171
|
|
|
152
172
|
end = time.time()
|
|
153
|
-
table.add_row(
|
|
154
|
-
|
|
173
|
+
table.add_row(
|
|
174
|
+
"[[green]Success[/green]]",
|
|
175
|
+
check_name,
|
|
176
|
+
check_type.replace("_", " ").title(),
|
|
177
|
+
f"{end - start:.2f} seconds",
|
|
178
|
+
"N/A",
|
|
179
|
+
)
|
|
155
180
|
except Exception as e:
|
|
156
181
|
rc = 1
|
|
157
182
|
if run is None:
|
|
158
|
-
table.add_row(
|
|
159
|
-
failed_checks.append(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
183
|
+
table.add_row("[[red]Error[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", str(e))
|
|
184
|
+
failed_checks.append(
|
|
185
|
+
{
|
|
186
|
+
"check_name": check_name,
|
|
187
|
+
"check_type": check_type,
|
|
188
|
+
"check_description": check_description,
|
|
189
|
+
"failed_type": "error",
|
|
190
|
+
"failed_reason": str(e),
|
|
191
|
+
}
|
|
192
|
+
)
|
|
166
193
|
else:
|
|
167
194
|
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
|
-
|
|
195
|
+
table.add_row("[[red]Failed[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", run.error)
|
|
196
|
+
failed_checks.append(
|
|
197
|
+
{
|
|
198
|
+
"check_name": check_name,
|
|
199
|
+
"check_type": check_type,
|
|
200
|
+
"check_description": "N/A",
|
|
201
|
+
"failed_type": "failed",
|
|
202
|
+
"failed_reason": run.error,
|
|
203
|
+
}
|
|
204
|
+
)
|
|
176
205
|
|
|
177
206
|
console.print(table)
|
|
178
207
|
return rc, failed_checks
|
|
179
208
|
|
|
180
209
|
|
|
181
|
-
async def execute_state_checks(checks:
|
|
210
|
+
async def execute_state_checks(checks: List, is_skip_query: bool) -> Tuple[int, List[Dict]]:
|
|
182
211
|
"""
|
|
183
212
|
Execute the checks from loaded state
|
|
184
213
|
"""
|
|
185
214
|
console = Console()
|
|
186
215
|
rc = 0
|
|
187
216
|
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(
|
|
217
|
+
table = Table(title="Recce Checks", box=box.HORIZONTALS, title_style="bold dark_orange3")
|
|
218
|
+
table.add_column("Status")
|
|
219
|
+
table.add_column("Name")
|
|
220
|
+
table.add_column("Type")
|
|
221
|
+
table.add_column("Execution Time")
|
|
222
|
+
table.add_column("Failed Reason")
|
|
194
223
|
|
|
195
224
|
# Execute loaded checks
|
|
196
225
|
for check in checks:
|
|
@@ -210,33 +239,42 @@ async def execute_state_checks(checks: list) -> (int, List[dict]):
|
|
|
210
239
|
raise ValueError(f"Invalid check type: {check_type}")
|
|
211
240
|
|
|
212
241
|
start = time.time()
|
|
213
|
-
if check_type not in [
|
|
242
|
+
if check_type not in ["schema_diff", "lineage_diff"] and not is_skip_query:
|
|
214
243
|
run, future = submit_run(check_type, params=check_params, check_id=check_id)
|
|
215
244
|
await future
|
|
216
245
|
|
|
217
246
|
end = time.time()
|
|
218
|
-
table.add_row(
|
|
219
|
-
|
|
247
|
+
table.add_row(
|
|
248
|
+
"[[green]Success[/green]]",
|
|
249
|
+
check_name,
|
|
250
|
+
check_type.replace("_", " ").title(),
|
|
251
|
+
f"{end - start:.2f} seconds",
|
|
252
|
+
"N/A",
|
|
253
|
+
)
|
|
220
254
|
except Exception as e:
|
|
221
255
|
rc = 1
|
|
222
256
|
if run is None:
|
|
223
|
-
table.add_row(
|
|
224
|
-
failed_checks.append(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
257
|
+
table.add_row("[[red]Error[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", str(e))
|
|
258
|
+
failed_checks.append(
|
|
259
|
+
{
|
|
260
|
+
"check_name": check_name,
|
|
261
|
+
"check_type": check_type,
|
|
262
|
+
"check_description": check_description,
|
|
263
|
+
"failed_type": "error",
|
|
264
|
+
"failed_reason": str(e),
|
|
265
|
+
}
|
|
266
|
+
)
|
|
231
267
|
else:
|
|
232
|
-
table.add_row(
|
|
233
|
-
failed_checks.append(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
268
|
+
table.add_row("[[red]Failed[/red]]", check_name, check_type.replace("_", " ").title(), "N/A", run.error)
|
|
269
|
+
failed_checks.append(
|
|
270
|
+
{
|
|
271
|
+
"check_name": check_name,
|
|
272
|
+
"check_type": check_type,
|
|
273
|
+
"check_description": "N/A",
|
|
274
|
+
"failed_type": "failed",
|
|
275
|
+
"failed_reason": run.error,
|
|
276
|
+
}
|
|
277
|
+
)
|
|
240
278
|
|
|
241
279
|
console.print(table)
|
|
242
280
|
return rc, failed_checks
|
|
@@ -244,24 +282,27 @@ async def execute_state_checks(checks: list) -> (int, List[dict]):
|
|
|
244
282
|
|
|
245
283
|
def process_failed_checks(failed_checks: List[dict], error_log=None):
|
|
246
284
|
from py_markdown_table.markdown_table import markdown_table
|
|
285
|
+
|
|
247
286
|
failed_check_table = []
|
|
248
287
|
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
|
-
|
|
288
|
+
name = check.get("check_name")
|
|
289
|
+
check_type = check.get("check_type")
|
|
290
|
+
failed_type = check.get("failed_type")
|
|
291
|
+
failed_reason = check.get("failed_reason")
|
|
292
|
+
failed_check_table.append(
|
|
293
|
+
{
|
|
294
|
+
"Name": name,
|
|
295
|
+
"Type": check_type,
|
|
296
|
+
"Kind of Failed": failed_type,
|
|
297
|
+
"Failed Reason": failed_reason.replace("\n", " "),
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
content = "# Recce Runc Failed Checks\n"
|
|
302
|
+
content += markdown_table(failed_check_table).set_params(quote=False, row_sep="markdown").get_markdown()
|
|
262
303
|
|
|
263
304
|
if error_log:
|
|
264
|
-
with open(error_log,
|
|
305
|
+
with open(error_log, "w", encoding="utf-8") as f:
|
|
265
306
|
f.write(content)
|
|
266
307
|
print(f"The failed checks are stored at '{error_log}'")
|
|
267
308
|
else:
|
|
@@ -271,62 +312,78 @@ def process_failed_checks(failed_checks: List[dict], error_log=None):
|
|
|
271
312
|
async def cli_run(output_state_file: str, **kwargs):
|
|
272
313
|
"""The main function of 'recce run' command. It will execute the default runs and store the state."""
|
|
273
314
|
console = Console()
|
|
274
|
-
error_log = kwargs.get(
|
|
275
|
-
if kwargs.get(
|
|
315
|
+
error_log = kwargs.get("error_log")
|
|
316
|
+
if kwargs.get("sqlmesh", False):
|
|
276
317
|
console.print("[[red]Error[/red]] SQLMesh adapter is not supported.")
|
|
277
318
|
sys.exit(1)
|
|
278
319
|
|
|
279
320
|
from recce.core import load_context
|
|
321
|
+
|
|
280
322
|
ctx = load_context(**kwargs)
|
|
281
323
|
|
|
282
|
-
|
|
324
|
+
# Set up the checks if this is a session-based run
|
|
325
|
+
if kwargs.get("session_id") and kwargs.get("state_loader"):
|
|
326
|
+
state_loader = kwargs.get("state_loader")
|
|
327
|
+
try:
|
|
328
|
+
# Try to populate the checks from the database
|
|
329
|
+
state_loader.state.checks = CheckDAO().list()
|
|
330
|
+
except Exception as e:
|
|
331
|
+
console.print(f"[[red]Error[/red]] Failed to load checks from database: {e}")
|
|
332
|
+
|
|
333
|
+
is_skip_query = kwargs.get("skip_query", False)
|
|
334
|
+
is_skip_check = kwargs.get("skip_check", False)
|
|
283
335
|
|
|
284
336
|
# Prepare the artifact by collecting the lineage
|
|
285
337
|
console.rule("DBT Artifacts")
|
|
286
338
|
from recce.adapter.dbt_adapter import DbtAdapter
|
|
339
|
+
|
|
287
340
|
dbt_adaptor: DbtAdapter = ctx.adapter
|
|
288
341
|
dbt_adaptor.print_lineage_info()
|
|
289
342
|
|
|
290
343
|
# Execute the preset checks
|
|
291
344
|
rc = 0
|
|
292
345
|
if ctx.state_loader.state is None:
|
|
293
|
-
preset_checks = RecceConfig().get(
|
|
294
|
-
if
|
|
346
|
+
preset_checks = RecceConfig().get("checks")
|
|
347
|
+
if is_skip_check or preset_checks is None or len(preset_checks) == 0:
|
|
295
348
|
# Skip the preset checks
|
|
296
349
|
pass
|
|
297
350
|
else:
|
|
298
351
|
console.rule("Preset checks")
|
|
299
|
-
_, failed_checks = await execute_preset_checks(preset_checks)
|
|
352
|
+
_, failed_checks = await execute_preset_checks(preset_checks, is_skip_query)
|
|
300
353
|
if failed_checks:
|
|
301
354
|
console.print("[[yellow]Warning[/yellow]] Preset checks failed. Please see the failed reason.")
|
|
302
355
|
process_failed_checks(failed_checks, error_log)
|
|
303
356
|
else:
|
|
304
357
|
state_checks = ctx.state_loader.state.checks
|
|
305
|
-
if
|
|
358
|
+
if is_skip_check or state_checks is None or len(state_checks) == 0:
|
|
306
359
|
# Skip the checks in the state
|
|
307
360
|
pass
|
|
308
361
|
else:
|
|
309
362
|
console.rule("Checks")
|
|
310
|
-
_, failed_checks = await execute_state_checks(state_checks)
|
|
363
|
+
_, failed_checks = await execute_state_checks(state_checks, is_skip_query)
|
|
311
364
|
if failed_checks:
|
|
312
365
|
console.print("[[yellow]Warning[/yellow]] Checks failed. Please see the failed reason.")
|
|
313
366
|
process_failed_checks(failed_checks, error_log)
|
|
314
367
|
|
|
315
368
|
from recce.event import log_load_state
|
|
316
|
-
|
|
369
|
+
|
|
370
|
+
log_load_state(command="run")
|
|
317
371
|
|
|
318
372
|
# Export the state
|
|
319
373
|
console.rule("Export state")
|
|
320
374
|
ctx.state_loader.state_file = output_state_file
|
|
321
375
|
msg = ctx.state_loader.export(ctx.export_state())
|
|
322
|
-
|
|
376
|
+
if msg is not None:
|
|
377
|
+
console.print(msg)
|
|
378
|
+
else:
|
|
379
|
+
console.print("Export successful")
|
|
323
380
|
|
|
324
|
-
summary_path = kwargs.get(
|
|
381
|
+
summary_path = kwargs.get("summary")
|
|
325
382
|
if summary_path:
|
|
326
383
|
dirs = os.path.dirname(summary_path)
|
|
327
384
|
if dirs:
|
|
328
385
|
os.makedirs(dirs, exist_ok=True)
|
|
329
|
-
with open(summary_path,
|
|
386
|
+
with open(summary_path, "w", encoding="utf-8") as f:
|
|
330
387
|
f.write(generate_markdown_summary(ctx))
|
|
331
388
|
console.print(f"The summary is stored at '{summary_path}'")
|
|
332
389
|
|