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/apis/check_func.py
CHANGED
|
@@ -5,13 +5,13 @@ from fastapi import HTTPException
|
|
|
5
5
|
|
|
6
6
|
from recce.apis.run_func import generate_run_name
|
|
7
7
|
from recce.core import default_context
|
|
8
|
-
from recce.models import
|
|
8
|
+
from recce.models import Check, CheckDAO, RunDAO, RunType
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def validate_schema_diff_check(params):
|
|
12
|
-
node_id = params.get(
|
|
12
|
+
node_id = params.get("node_id")
|
|
13
13
|
if node_id is None:
|
|
14
|
-
raise HTTPException(status_code=400, detail=
|
|
14
|
+
raise HTTPException(status_code=400, detail="node_id is required for schema diff")
|
|
15
15
|
node_name = default_context().get_node_name_by_id(node_id)
|
|
16
16
|
if node_name is None:
|
|
17
17
|
raise HTTPException(status_code=400, detail=f"node_id '{node_id}' not found in dbt manifest")
|
|
@@ -45,8 +45,8 @@ def _get_ref_model(sql_template: str) -> Optional[str]:
|
|
|
45
45
|
def _generate_check_name(check_type, params, view_options):
|
|
46
46
|
now = datetime.utcnow().strftime("%d %b %Y")
|
|
47
47
|
if check_type == RunType.SCHEMA_DIFF:
|
|
48
|
-
if params.get(
|
|
49
|
-
nodeIds = params.get(
|
|
48
|
+
if params.get("node_id"):
|
|
49
|
+
nodeIds = params.get("node_id") if isinstance(params.get("node_id"), list) else [params.get("node_id")]
|
|
50
50
|
if len(nodeIds) == 1:
|
|
51
51
|
node_name = get_node_name_by_id(nodeIds[0])
|
|
52
52
|
return f"schema diff of {node_name}".capitalize()
|
|
@@ -54,7 +54,7 @@ def _generate_check_name(check_type, params, view_options):
|
|
|
54
54
|
return f"schema diff of {len(nodeIds)} nodes".capitalize()
|
|
55
55
|
return f"{'schema diff'.capitalize()} - {now}"
|
|
56
56
|
elif check_type == RunType.LINEAGE_DIFF:
|
|
57
|
-
nodes = view_options.get(
|
|
57
|
+
nodes = view_options.get("node_ids") if view_options else params.get("node_ids")
|
|
58
58
|
if nodes is not None:
|
|
59
59
|
return f"lineage diff of {len(nodes)} nodes".capitalize()
|
|
60
60
|
return f"{'lineage diff'.capitalize()} - {now}"
|
|
@@ -62,10 +62,11 @@ def _generate_check_name(check_type, params, view_options):
|
|
|
62
62
|
return f"{'check'.capitalize()} - {now}"
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def create_check_from_run(
|
|
66
|
-
|
|
65
|
+
def create_check_from_run(
|
|
66
|
+
run_id, check_name=None, check_description="", check_view_options=None, is_preset=False, is_checked=False
|
|
67
|
+
):
|
|
67
68
|
if run_id is None:
|
|
68
|
-
raise ValueError(
|
|
69
|
+
raise ValueError("run_id is required")
|
|
69
70
|
|
|
70
71
|
run = RunDAO().find_run_by_id(run_id)
|
|
71
72
|
if run is None:
|
|
@@ -76,31 +77,36 @@ def create_check_from_run(run_id, check_name=None, check_description='', check_v
|
|
|
76
77
|
|
|
77
78
|
_validate_check(run_type, run_params)
|
|
78
79
|
name = check_name if check_name is not None else generate_run_name(run)
|
|
79
|
-
check = Check(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
check = Check(
|
|
81
|
+
name=name,
|
|
82
|
+
description=check_description,
|
|
83
|
+
type=run_type,
|
|
84
|
+
params=run_params,
|
|
85
|
+
view_options=check_view_options,
|
|
86
|
+
is_preset=is_preset,
|
|
87
|
+
is_checked=is_checked,
|
|
88
|
+
)
|
|
89
|
+
new_check = CheckDAO().create(check)
|
|
90
|
+
run.check_id = new_check.check_id
|
|
91
|
+
|
|
92
|
+
return new_check
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def create_check_without_run(
|
|
96
|
+
check_name, check_description, check_type, params, check_view_options, is_preset=False, is_checked=False
|
|
97
|
+
):
|
|
94
98
|
name = check_name if check_name is not None else _generate_check_name(check_type, params, check_view_options)
|
|
95
|
-
check = Check(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
check = Check(
|
|
100
|
+
name=name,
|
|
101
|
+
description=check_description,
|
|
102
|
+
type=check_type,
|
|
103
|
+
params=params,
|
|
104
|
+
view_options=check_view_options,
|
|
105
|
+
is_preset=is_preset,
|
|
106
|
+
is_checked=is_checked,
|
|
107
|
+
)
|
|
108
|
+
new_check = CheckDAO().create(check)
|
|
109
|
+
return new_check
|
|
104
110
|
|
|
105
111
|
|
|
106
112
|
def purge_preset_checks():
|
|
@@ -119,6 +125,6 @@ def export_persistent_state():
|
|
|
119
125
|
if state_loader is not None:
|
|
120
126
|
is_conflict = state_loader.check_conflict()
|
|
121
127
|
if is_conflict:
|
|
122
|
-
ctx.sync_state(
|
|
128
|
+
ctx.sync_state("merge")
|
|
123
129
|
else:
|
|
124
|
-
ctx.sync_state(
|
|
130
|
+
ctx.sync_state("overwrite")
|
recce/apis/run_api.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import List, Optional
|
|
3
3
|
from uuid import UUID
|
|
4
4
|
|
|
5
5
|
from fastapi import APIRouter, HTTPException, Query
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from recce.apis.run_func import
|
|
8
|
+
from recce.apis.run_func import cancel_run, materialize_run_results, submit_run
|
|
9
9
|
from recce.event import log_api_event
|
|
10
10
|
from recce.exceptions import RecceException
|
|
11
11
|
from recce.models import RunDAO
|
|
12
12
|
|
|
13
|
-
run_router = APIRouter(tags=[
|
|
13
|
+
run_router = APIRouter(tags=["run"])
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class CreateRunIn(BaseModel):
|
|
@@ -23,10 +23,13 @@ class CreateRunIn(BaseModel):
|
|
|
23
23
|
|
|
24
24
|
@run_router.post("/runs", status_code=201)
|
|
25
25
|
async def create_run_handler(input: CreateRunIn):
|
|
26
|
-
log_api_event(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
log_api_event(
|
|
27
|
+
"create_run",
|
|
28
|
+
dict(
|
|
29
|
+
type=input.type,
|
|
30
|
+
track_props=input.track_props,
|
|
31
|
+
),
|
|
32
|
+
)
|
|
30
33
|
try:
|
|
31
34
|
run, future = submit_run(input.type, input.params)
|
|
32
35
|
except RecceException as e:
|
|
@@ -51,7 +54,7 @@ async def cancel_run_handler(run_id: UUID):
|
|
|
51
54
|
async def wait_run_handler(run_id: UUID, timeout: int = Query(None, description="Maximum number of seconds to wait")):
|
|
52
55
|
run = RunDAO().find_run_by_id(run_id)
|
|
53
56
|
if run is None:
|
|
54
|
-
raise HTTPException(status_code=404, detail=
|
|
57
|
+
raise HTTPException(status_code=404, detail="Not Found")
|
|
55
58
|
|
|
56
59
|
start_time = asyncio.get_event_loop().time()
|
|
57
60
|
while run.result is None and run.error is None:
|
|
@@ -65,18 +68,21 @@ async def wait_run_handler(run_id: UUID, timeout: int = Query(None, description=
|
|
|
65
68
|
async def list_run_handler():
|
|
66
69
|
runs = RunDAO().list() or []
|
|
67
70
|
|
|
68
|
-
result = [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
result = [
|
|
72
|
+
{
|
|
73
|
+
"run_id": run.run_id,
|
|
74
|
+
"run_at": run.run_at,
|
|
75
|
+
"name": run.name,
|
|
76
|
+
"type": run.type,
|
|
77
|
+
"params": run.params,
|
|
78
|
+
"status": run.status,
|
|
79
|
+
"check_id": run.check_id,
|
|
80
|
+
}
|
|
81
|
+
for run in runs
|
|
82
|
+
]
|
|
77
83
|
|
|
78
84
|
# sort by run_at
|
|
79
|
-
result = sorted(result, key=lambda x: x[
|
|
85
|
+
result = sorted(result, key=lambda x: x["run_at"], reverse=True)
|
|
80
86
|
|
|
81
87
|
return result
|
|
82
88
|
|
|
@@ -101,7 +107,7 @@ async def search_runs_handler(search: SearchRunsIn):
|
|
|
101
107
|
result.append(run)
|
|
102
108
|
|
|
103
109
|
if search.limit:
|
|
104
|
-
return result[-search.limit:]
|
|
110
|
+
return result[-search.limit :]
|
|
105
111
|
|
|
106
112
|
return result
|
|
107
113
|
|
recce/apis/run_func.py
CHANGED
|
@@ -4,11 +4,11 @@ from typing import List, Optional
|
|
|
4
4
|
|
|
5
5
|
from recce.core import default_context
|
|
6
6
|
from recce.exceptions import RecceException
|
|
7
|
-
from recce.models import
|
|
7
|
+
from recce.models import Run, RunDAO, RunType
|
|
8
8
|
from recce.models.types import RunStatus
|
|
9
9
|
|
|
10
10
|
running_tasks = {}
|
|
11
|
-
logger = logging.getLogger(
|
|
11
|
+
logger = logging.getLogger("uvicorn")
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _get_ref_model(sql_template: str) -> Optional[str]:
|
|
@@ -33,26 +33,26 @@ def generate_run_name(run):
|
|
|
33
33
|
now = dateutil.parser.parse(run.run_at)
|
|
34
34
|
|
|
35
35
|
if run_type == RunType.QUERY:
|
|
36
|
-
ref = _get_ref_model(params.get(
|
|
36
|
+
ref = _get_ref_model(params.get("sql_template"))
|
|
37
37
|
if ref:
|
|
38
38
|
return f"query of {ref}".capitalize()
|
|
39
39
|
return f"{'query'.capitalize()} - {now}"
|
|
40
40
|
elif run_type == RunType.QUERY_DIFF:
|
|
41
|
-
ref = _get_ref_model(params.get(
|
|
41
|
+
ref = _get_ref_model(params.get("sql_template"))
|
|
42
42
|
if ref:
|
|
43
43
|
return f"query diff of {ref}".capitalize()
|
|
44
44
|
return f"{'query diff'.capitalize()} - {now}"
|
|
45
45
|
elif run_type == RunType.VALUE_DIFF:
|
|
46
|
-
model = params.get(
|
|
46
|
+
model = params.get("model")
|
|
47
47
|
return f"value diff of {model}".capitalize()
|
|
48
48
|
elif run_type == RunType.VALUE_DIFF_DETAIL:
|
|
49
|
-
model = params.get(
|
|
49
|
+
model = params.get("model")
|
|
50
50
|
return f"value diff detail of {model}".capitalize()
|
|
51
51
|
elif run_type == RunType.PROFILE_DIFF:
|
|
52
|
-
model = params.get(
|
|
52
|
+
model = params.get("model")
|
|
53
53
|
return f"profile diff of {model}".capitalize()
|
|
54
54
|
elif run_type == RunType.ROW_COUNT_DIFF:
|
|
55
|
-
nodes = params.get(
|
|
55
|
+
nodes = params.get("node_names")
|
|
56
56
|
if nodes:
|
|
57
57
|
if len(nodes) == 1:
|
|
58
58
|
node = nodes[0]
|
|
@@ -62,23 +62,27 @@ def generate_run_name(run):
|
|
|
62
62
|
else:
|
|
63
63
|
return "row count of multiple nodes".capitalize()
|
|
64
64
|
elif run_type == RunType.TOP_K_DIFF:
|
|
65
|
-
model = params.get(
|
|
66
|
-
column = params.get(
|
|
65
|
+
model = params.get("model")
|
|
66
|
+
column = params.get("column_name")
|
|
67
67
|
return f"top-k diff of {model}.{column} ".capitalize()
|
|
68
68
|
elif run_type == RunType.HISTOGRAM_DIFF:
|
|
69
|
-
model = params.get(
|
|
70
|
-
column = params.get(
|
|
69
|
+
model = params.get("model")
|
|
70
|
+
column = params.get("column_name")
|
|
71
71
|
return f"histogram diff of {model}.{column} ".capitalize()
|
|
72
72
|
else:
|
|
73
73
|
return f"{'run'.capitalize()} - {now}"
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def create_task(run_type: RunType, params: dict):
|
|
77
|
-
if default_context().adapter_type ==
|
|
78
|
-
from recce.adapter.sqlmesh_adapter import
|
|
77
|
+
if default_context().adapter_type == "sqlmesh":
|
|
78
|
+
from recce.adapter.sqlmesh_adapter import (
|
|
79
|
+
sqlmesh_supported_registry as sqlmesh_registry,
|
|
80
|
+
)
|
|
81
|
+
|
|
79
82
|
registry = sqlmesh_registry
|
|
80
83
|
else:
|
|
81
84
|
from recce.adapter.dbt_adapter import dbt_supported_registry as dbt_registry
|
|
85
|
+
|
|
82
86
|
registry = dbt_registry
|
|
83
87
|
|
|
84
88
|
taskClz = registry.get(run_type)
|
|
@@ -101,6 +105,7 @@ def submit_run(type, params, check_id=None):
|
|
|
101
105
|
context = default_context()
|
|
102
106
|
if context.review_mode is True:
|
|
103
107
|
from recce.adapter.dbt_adapter import DbtAdapter
|
|
108
|
+
|
|
104
109
|
dbt_adaptor: DbtAdapter = context.adapter
|
|
105
110
|
if dbt_adaptor.adapter is None:
|
|
106
111
|
raise RecceException("Recce Server is not launched under DBT project folder.")
|
|
@@ -113,18 +118,23 @@ def submit_run(type, params, check_id=None):
|
|
|
113
118
|
running_tasks[run.run_id] = task
|
|
114
119
|
|
|
115
120
|
def progress_listener(message=None, percentage=None):
|
|
116
|
-
run.progress = {
|
|
121
|
+
run.progress = {"message": message, "percentage": percentage}
|
|
117
122
|
|
|
118
123
|
task.progress_listener = progress_listener
|
|
119
124
|
|
|
120
|
-
async def update_run_result(
|
|
125
|
+
async def update_run_result(run, result, error, updated_params=None):
|
|
126
|
+
"""Update run with result, error, and optionally updated params."""
|
|
121
127
|
if run is None:
|
|
122
128
|
return
|
|
123
129
|
if result is not None:
|
|
124
130
|
run.result = result
|
|
125
131
|
run.status = RunStatus.FINISHED
|
|
132
|
+
if updated_params is not None:
|
|
133
|
+
# Merge updated params (preserves any fields not in updated_params)
|
|
134
|
+
run.params.update(updated_params)
|
|
126
135
|
if error is not None:
|
|
127
|
-
|
|
136
|
+
failed_reason = str(error) if str(error) != "None" else repr(error)
|
|
137
|
+
run.error = failed_reason
|
|
128
138
|
if run.status != RunStatus.CANCELLED:
|
|
129
139
|
run.status = RunStatus.FAILED
|
|
130
140
|
run.progress = None
|
|
@@ -132,13 +142,42 @@ def submit_run(type, params, check_id=None):
|
|
|
132
142
|
def fn():
|
|
133
143
|
try:
|
|
134
144
|
result = task.execute()
|
|
135
|
-
|
|
145
|
+
|
|
146
|
+
# Extract updated params from task after execution
|
|
147
|
+
updated_params = None
|
|
148
|
+
if hasattr(task, "params") and task.params is not None:
|
|
149
|
+
# Serialization logic:
|
|
150
|
+
# - Most tasks use Pydantic models (v2: model_dump, v1: dict)
|
|
151
|
+
# - Some tasks may use plain dicts
|
|
152
|
+
# - If params is an unexpected type, log a warning for debugging
|
|
153
|
+
# - Handle the case where model_dump() or dict() raises an exception.
|
|
154
|
+
try:
|
|
155
|
+
if hasattr(task.params, "model_dump"):
|
|
156
|
+
updated_params = task.params.model_dump()
|
|
157
|
+
elif hasattr(task.params, "dict"):
|
|
158
|
+
updated_params = task.params.dict()
|
|
159
|
+
elif isinstance(task.params, dict):
|
|
160
|
+
updated_params = task.params
|
|
161
|
+
else:
|
|
162
|
+
logger.warning(
|
|
163
|
+
f"Could not serialize task.params for run_id={run.run_id}: "
|
|
164
|
+
f"unexpected type {type(task.params)} with value {repr(task.params)}"
|
|
165
|
+
)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.warning(f"Failed to serialize task.params: {e}")
|
|
168
|
+
updated_params = None
|
|
169
|
+
|
|
170
|
+
asyncio.run_coroutine_threadsafe(update_run_result(run, result, None, updated_params), loop)
|
|
136
171
|
return result
|
|
137
172
|
except BaseException as e:
|
|
138
|
-
asyncio.run_coroutine_threadsafe(update_run_result(run
|
|
173
|
+
asyncio.run_coroutine_threadsafe(update_run_result(run, None, e, None), loop)
|
|
139
174
|
if isinstance(e, RecceException) and e.is_raise is False:
|
|
140
175
|
return None
|
|
141
|
-
|
|
176
|
+
import sentry_sdk
|
|
177
|
+
|
|
178
|
+
sentry_sdk.capture_exception(e)
|
|
179
|
+
failed_reason = str(e) if str(e) != "None" else repr(e)
|
|
180
|
+
failed_reason = failed_reason.replace(". ", ".\n")
|
|
142
181
|
logger.error(f"Failed to execute {run_type} task: {failed_reason}")
|
|
143
182
|
return None
|
|
144
183
|
|
|
@@ -160,7 +199,7 @@ def cancel_run(run_id):
|
|
|
160
199
|
|
|
161
200
|
|
|
162
201
|
def materialize_run_results(runs: List[Run], nodes: List[str] = None):
|
|
163
|
-
|
|
202
|
+
"""
|
|
164
203
|
Materialize the run results for nodes. It walks through all runs and get the last results for primary run types.
|
|
165
204
|
|
|
166
205
|
The result format
|
|
@@ -176,11 +215,11 @@ def materialize_run_results(runs: List[Run], nodes: List[str] = None):
|
|
|
176
215
|
},
|
|
177
216
|
},
|
|
178
217
|
}
|
|
179
|
-
|
|
218
|
+
"""
|
|
180
219
|
|
|
181
220
|
context = default_context()
|
|
182
221
|
if context:
|
|
183
|
-
mame_to_unique_id = context.build_name_to_unique_id_index(excluded_types={
|
|
222
|
+
mame_to_unique_id = context.build_name_to_unique_id_index(excluded_types={"semantic_model", "metric"})
|
|
184
223
|
else:
|
|
185
224
|
mame_to_unique_id = {}
|
|
186
225
|
|
|
@@ -201,7 +240,7 @@ def materialize_run_results(runs: List[Run], nodes: List[str] = None):
|
|
|
201
240
|
node_result = result[key] = {}
|
|
202
241
|
else:
|
|
203
242
|
node_result = result.get(key)
|
|
204
|
-
node_result[
|
|
243
|
+
node_result["row_count_diff"] = {"run_id": run.run_id, "result": node_run_result}
|
|
205
244
|
elif run.type == RunType.ROW_COUNT:
|
|
206
245
|
for model_name, node_run_result in run.result.items():
|
|
207
246
|
key = mame_to_unique_id.get(model_name, model_name)
|
|
@@ -214,5 +253,5 @@ def materialize_run_results(runs: List[Run], nodes: List[str] = None):
|
|
|
214
253
|
node_result = result[key] = {}
|
|
215
254
|
else:
|
|
216
255
|
node_result = result.get(key)
|
|
217
|
-
node_result[
|
|
256
|
+
node_result["row_count"] = {"run_id": run.run_id, "result": node_run_result}
|
|
218
257
|
return result
|