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.

Files changed (245) hide show
  1. recce/VERSION +1 -1
  2. recce/__init__.py +27 -22
  3. recce/adapter/base.py +11 -14
  4. recce/adapter/dbt_adapter/__init__.py +845 -461
  5. recce/adapter/dbt_adapter/dbt_version.py +3 -0
  6. recce/adapter/sqlmesh_adapter.py +24 -35
  7. recce/apis/check_api.py +59 -42
  8. recce/apis/check_events_api.py +353 -0
  9. recce/apis/check_func.py +41 -35
  10. recce/apis/run_api.py +25 -19
  11. recce/apis/run_func.py +64 -25
  12. recce/artifact.py +119 -51
  13. recce/cli.py +1301 -324
  14. recce/config.py +43 -34
  15. recce/connect_to_cloud.py +138 -0
  16. recce/core.py +55 -47
  17. recce/data/404/index.html +2 -0
  18. recce/data/404.html +2 -1
  19. recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
  20. recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
  21. recce/data/__next.__PAGE__.txt +6 -0
  22. recce/data/__next._full.txt +32 -0
  23. recce/data/__next._head.txt +8 -0
  24. recce/data/__next._index.txt +14 -0
  25. recce/data/__next._tree.txt +8 -0
  26. recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
  27. recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
  28. recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
  29. recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
  30. recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
  31. recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
  32. recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
  33. recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
  34. recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
  35. recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
  36. recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
  37. recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
  38. recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
  39. recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
  40. recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
  41. recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
  42. recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
  43. recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
  44. recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
  45. recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
  46. recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
  47. recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
  48. recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
  49. recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
  50. recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
  51. recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
  52. recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
  53. recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
  54. recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
  55. recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
  56. recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
  57. recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
  58. recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
  59. recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
  60. recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
  61. recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
  62. recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
  63. recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
  64. recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
  65. recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
  66. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
  67. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
  68. recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
  69. recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
  70. recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
  71. recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
  72. recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
  73. recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
  74. recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
  75. recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
  76. recce/data/_not-found/__next._full.txt +24 -0
  77. recce/data/_not-found/__next._head.txt +8 -0
  78. recce/data/_not-found/__next._index.txt +13 -0
  79. recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
  80. recce/data/_not-found/__next._not-found.txt +4 -0
  81. recce/data/_not-found/__next._tree.txt +6 -0
  82. recce/data/_not-found/index.html +2 -0
  83. recce/data/_not-found/index.txt +24 -0
  84. recce/data/auth_callback.html +68 -0
  85. recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
  86. recce/data/checks/__next._full.txt +39 -0
  87. recce/data/checks/__next._head.txt +8 -0
  88. recce/data/checks/__next._index.txt +14 -0
  89. recce/data/checks/__next._tree.txt +8 -0
  90. recce/data/checks/__next.checks.__PAGE__.txt +10 -0
  91. recce/data/checks/__next.checks.txt +4 -0
  92. recce/data/checks/index.html +2 -0
  93. recce/data/checks/index.txt +39 -0
  94. recce/data/imgs/reload-image.svg +4 -0
  95. recce/data/index.html +2 -27
  96. recce/data/index.txt +32 -7
  97. recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
  98. recce/data/lineage/__next._full.txt +39 -0
  99. recce/data/lineage/__next._head.txt +8 -0
  100. recce/data/lineage/__next._index.txt +14 -0
  101. recce/data/lineage/__next._tree.txt +8 -0
  102. recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
  103. recce/data/lineage/__next.lineage.txt +4 -0
  104. recce/data/lineage/index.html +2 -0
  105. recce/data/lineage/index.txt +39 -0
  106. recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
  107. recce/data/query/__next._full.txt +37 -0
  108. recce/data/query/__next._head.txt +8 -0
  109. recce/data/query/__next._index.txt +14 -0
  110. recce/data/query/__next._tree.txt +8 -0
  111. recce/data/query/__next.query.__PAGE__.txt +9 -0
  112. recce/data/query/__next.query.txt +4 -0
  113. recce/data/query/index.html +2 -0
  114. recce/data/query/index.txt +37 -0
  115. recce/diff.py +6 -12
  116. recce/event/CONFIG.bak +1 -0
  117. recce/event/__init__.py +86 -74
  118. recce/event/collector.py +33 -22
  119. recce/event/track.py +49 -27
  120. recce/exceptions.py +1 -1
  121. recce/git.py +7 -7
  122. recce/github.py +57 -53
  123. recce/mcp_server.py +725 -0
  124. recce/models/__init__.py +4 -1
  125. recce/models/check.py +438 -21
  126. recce/models/run.py +1 -0
  127. recce/models/types.py +134 -28
  128. recce/pull_request.py +27 -25
  129. recce/run.py +179 -122
  130. recce/server.py +394 -104
  131. recce/state/__init__.py +31 -0
  132. recce/state/cloud.py +644 -0
  133. recce/state/const.py +26 -0
  134. recce/state/local.py +56 -0
  135. recce/state/state.py +119 -0
  136. recce/state/state_loader.py +174 -0
  137. recce/summary.py +196 -149
  138. recce/tasks/__init__.py +19 -3
  139. recce/tasks/core.py +11 -13
  140. recce/tasks/dataframe.py +82 -18
  141. recce/tasks/histogram.py +69 -34
  142. recce/tasks/lineage.py +2 -2
  143. recce/tasks/profile.py +152 -86
  144. recce/tasks/query.py +180 -89
  145. recce/tasks/rowcount.py +37 -31
  146. recce/tasks/schema.py +18 -15
  147. recce/tasks/top_k.py +35 -35
  148. recce/tasks/utils.py +147 -0
  149. recce/tasks/valuediff.py +247 -155
  150. recce/util/__init__.py +3 -0
  151. recce/util/api_token.py +80 -0
  152. recce/util/breaking.py +105 -100
  153. recce/util/cll.py +274 -219
  154. recce/util/cloud/__init__.py +15 -0
  155. recce/util/cloud/base.py +115 -0
  156. recce/util/cloud/check_events.py +190 -0
  157. recce/util/cloud/checks.py +242 -0
  158. recce/util/io.py +22 -17
  159. recce/util/lineage.py +65 -16
  160. recce/util/logger.py +1 -1
  161. recce/util/onboarding_state.py +45 -0
  162. recce/util/perf_tracking.py +85 -0
  163. recce/util/recce_cloud.py +347 -72
  164. recce/util/singleton.py +4 -4
  165. recce/util/startup_perf.py +121 -0
  166. recce/yaml/__init__.py +7 -10
  167. recce_nightly-1.30.0.20251221.dist-info/METADATA +195 -0
  168. recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
  169. {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
  170. recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
  171. recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
  172. recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
  173. recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
  174. recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
  175. recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
  176. recce/data/_next/static/chunks/500-e51c92a025a51234.js +0 -65
  177. recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
  178. recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
  179. recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
  180. recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
  181. recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
  182. recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
  183. recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
  184. recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
  185. recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +0 -1
  186. recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
  187. recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
  188. recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
  189. recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
  190. recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
  191. recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
  192. recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
  193. recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
  194. recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
  195. recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
  196. recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
  197. recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
  198. recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
  199. recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
  200. recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
  201. recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
  202. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
  203. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
  204. recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
  205. recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
  206. recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
  207. recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
  208. recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
  209. recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
  210. recce/data/_next/static/qiyFlux77VkhxiceAJe_F/_buildManifest.js +0 -1
  211. recce/state.py +0 -753
  212. recce_nightly-0.62.0.20250417.dist-info/METADATA +0 -311
  213. recce_nightly-0.62.0.20250417.dist-info/RECORD +0 -139
  214. recce_nightly-0.62.0.20250417.dist-info/top_level.txt +0 -2
  215. tests/__init__.py +0 -0
  216. tests/adapter/__init__.py +0 -0
  217. tests/adapter/dbt_adapter/__init__.py +0 -0
  218. tests/adapter/dbt_adapter/conftest.py +0 -13
  219. tests/adapter/dbt_adapter/dbt_test_helper.py +0 -283
  220. tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -40
  221. tests/adapter/dbt_adapter/test_dbt_cll.py +0 -102
  222. tests/adapter/dbt_adapter/test_selector.py +0 -177
  223. tests/tasks/__init__.py +0 -0
  224. tests/tasks/conftest.py +0 -4
  225. tests/tasks/test_histogram.py +0 -137
  226. tests/tasks/test_lineage.py +0 -42
  227. tests/tasks/test_preset_checks.py +0 -50
  228. tests/tasks/test_profile.py +0 -73
  229. tests/tasks/test_query.py +0 -151
  230. tests/tasks/test_row_count.py +0 -116
  231. tests/tasks/test_schema.py +0 -99
  232. tests/tasks/test_top_k.py +0 -73
  233. tests/tasks/test_valuediff.py +0 -74
  234. tests/test_cli.py +0 -122
  235. tests/test_config.py +0 -45
  236. tests/test_core.py +0 -27
  237. tests/test_dbt.py +0 -36
  238. tests/test_pull_request.py +0 -130
  239. tests/test_server.py +0 -98
  240. tests/test_state.py +0 -123
  241. tests/test_summary.py +0 -57
  242. /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
  243. /recce/data/_next/static/{qiyFlux77VkhxiceAJe_F → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
  244. {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
  245. {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 create_check_from_run, create_check_without_run, purge_preset_checks
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 'GITHUB_ACTIONS' not in os.environ:
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('GITHUB_SERVER_URL', 'https://github.com')
27
- github_repository = os.environ.get('GITHUB_REPOSITORY', '')
28
- github_ref_name = os.environ.get('GITHUB_REF_NAME', '')
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='Recce Preset Checks', box=box.HORIZONTALS, title_style='bold dark_orange3')
37
- table.add_column('Name')
38
- table.add_column('Type')
39
- table.add_column('Description')
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('name')
43
- description = check.get('description', '')
44
- check_type = check.get('type')
45
- check_params = check.get('params', {})
46
- check_options = check.get('view_options', {})
47
-
48
- create_check_without_run(name, description, check_type, check_params, check_options, is_preset=True,
49
- is_checked=is_check)
50
- table.add_row(name, check_type.replace('_', ' ').title(), description.strip())
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 'node_id' in check_params:
64
+ if "node_id" in check_params:
59
65
  # If the node_id is provided, then use it
60
- if isinstance(check_params['node_id'], str):
61
- selected_node_ids = [check_params['node_id']]
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('node_id', [])
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('select'),
68
- exclude=check_params.get('exclude'),
69
- packages=check_params.get('packages'),
70
- view_mode=check_params.get('view_mode'),
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('test.')]
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('nodes', {}).items():
83
+ for node_id, node in lineage.get("nodes", {}).items():
78
84
  if node_id in node_ids:
79
- nodes[node_id] = node.get('columns', {})
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['base'] != row_count_result['curr']:
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: list) -> (int, List[dict]):
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='Recce Preset Checks', box=box.HORIZONTALS, title_style='bold dark_orange3')
117
- table.add_column('Status')
118
- table.add_column('Name')
119
- table.add_column('Type')
120
- table.add_column('Execution Time')
121
- table.add_column('Failed Reason')
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('name')
130
- check_type = check.get('type')
131
- check_description = check.get('description', '')
132
- check_params = check.get('params') if check.get('params') else {}
133
- check_options = check.get('view_options', {})
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 ['schema_diff', 'lineage_diff']:
142
- is_check = schema_diff_should_be_approved(check_params) if check_type == 'schema_diff' else False
143
- create_check_without_run(check_name, check_description, check_type, check_params, check_options,
144
- is_preset=True, is_checked=is_check)
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
- run, future = submit_run(check_type, params=check_params)
147
- await future
148
- is_check = run_should_be_approved(run)
149
- create_check_from_run(run.run_id, check_name, check_description, check_options, is_preset=True,
150
- is_checked=is_check)
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('[[green]Success[/green]]', check_name, check_type.replace('_', ' ').title(),
154
- f'{end - start:.2f} seconds', 'N/A')
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('[[red]Error[/red]]', check_name, check_type.replace('_', ' ').title(), 'N/A', str(e))
159
- failed_checks.append({
160
- 'check_name': check_name,
161
- 'check_type': check_type,
162
- 'check_description': check_description,
163
- 'failed_type': 'error',
164
- 'failed_reason': str(e)
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('[[red]Failed[/red]]', check_name, check_type.replace('_', ' ').title(), 'N/A', run.error)
169
- failed_checks.append({
170
- 'check_name': check_name,
171
- 'check_type': check_type,
172
- 'check_description': 'N/A',
173
- 'failed_type': 'failed',
174
- 'failed_reason': run.error
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: list) -> (int, List[dict]):
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='Recce Checks', box=box.HORIZONTALS, title_style='bold dark_orange3')
189
- table.add_column('Status')
190
- table.add_column('Name')
191
- table.add_column('Type')
192
- table.add_column('Execution Time')
193
- table.add_column('Failed Reason')
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 ['schema_diff']:
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('[[green]Success[/green]]', check_name, check_type.replace('_', ' ').title(),
219
- f'{end - start:.2f} seconds', 'N/A')
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('[[red]Error[/red]]', check_name, check_type.replace('_', ' ').title(), 'N/A', str(e))
224
- failed_checks.append({
225
- 'check_name': check_name,
226
- 'check_type': check_type,
227
- 'check_description': check_description,
228
- 'failed_type': 'error',
229
- 'failed_reason': str(e)
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('[[red]Failed[/red]]', check_name, check_type.replace('_', ' ').title(), 'N/A', run.error)
233
- failed_checks.append({
234
- 'check_name': check_name,
235
- 'check_type': check_type,
236
- 'check_description': 'N/A',
237
- 'failed_type': 'failed',
238
- 'failed_reason': run.error
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('check_name')
250
- check_type = check.get('check_type')
251
- failed_type = check.get('failed_type')
252
- failed_reason = check.get('failed_reason')
253
- failed_check_table.append({
254
- 'Name': name,
255
- 'Type': check_type,
256
- 'Kind of Failed': failed_type,
257
- 'Failed Reason': failed_reason.replace('\n', ' ')
258
- })
259
-
260
- content = '# Recce Runc Failed Checks\n'
261
- content += markdown_table(failed_check_table).set_params(quote=False, row_sep='markdown').get_markdown()
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, 'w') as f:
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('error_log')
275
- if kwargs.get('sqlmesh', False):
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
- is_skip_query = kwargs.get('skip_query', False)
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('checks')
294
- if is_skip_query or preset_checks is None or len(preset_checks) == 0:
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 is_skip_query or state_checks is None or len(state_checks) == 0:
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
- log_load_state(command='run')
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
- console.print(msg)
376
+ if msg is not None:
377
+ console.print(msg)
378
+ else:
379
+ console.print("Export successful")
323
380
 
324
- summary_path = kwargs.get('summary')
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, 'w') as f:
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