recce-nightly 1.10.0.20250625__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 (229) hide show
  1. recce/VERSION +1 -1
  2. recce/__init__.py +5 -0
  3. recce/adapter/dbt_adapter/__init__.py +343 -245
  4. recce/apis/check_api.py +20 -14
  5. recce/apis/check_events_api.py +353 -0
  6. recce/apis/check_func.py +5 -5
  7. recce/apis/run_func.py +32 -3
  8. recce/artifact.py +76 -3
  9. recce/cli.py +705 -82
  10. recce/config.py +2 -2
  11. recce/connect_to_cloud.py +1 -1
  12. recce/core.py +3 -3
  13. recce/data/404/index.html +2 -0
  14. recce/data/404.html +2 -22
  15. recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
  16. recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
  17. recce/data/__next.__PAGE__.txt +6 -0
  18. recce/data/__next._full.txt +32 -0
  19. recce/data/__next._head.txt +8 -0
  20. recce/data/__next._index.txt +14 -0
  21. recce/data/__next._tree.txt +8 -0
  22. recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
  23. recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
  24. recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
  25. recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
  26. recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
  27. recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
  28. recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
  29. recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
  30. recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
  31. recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
  32. recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
  33. recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
  34. recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
  35. recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
  36. recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
  37. recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
  38. recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
  39. recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
  40. recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
  41. recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
  42. recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
  43. recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
  44. recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
  45. recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
  46. recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
  47. recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
  48. recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
  49. recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
  50. recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
  51. recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
  52. recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
  53. recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
  54. recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
  55. recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
  56. recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
  57. recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
  58. recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
  59. recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
  60. recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
  61. recce/data/_next/static/media/{montserrat-cyrillic-800-normal.bd5c9f50.woff → montserrat-cyrillic-800-normal.f9d58125.woff} +0 -0
  62. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
  63. recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
  64. recce/data/_next/static/media/{montserrat-latin-800-normal.fc315020.woff → montserrat-latin-800-normal.d5761935.woff} +0 -0
  65. recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
  66. recce/data/_next/static/media/{montserrat-latin-ext-800-normal.2e5381b2.woff → montserrat-latin-ext-800-normal.b671449b.woff} +0 -0
  67. recce/data/_next/static/media/{montserrat-vietnamese-800-normal.20c545e6.woff → montserrat-vietnamese-800-normal.9f7b8541.woff} +0 -0
  68. recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
  69. recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
  70. recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
  71. recce/data/_not-found/__next._full.txt +24 -0
  72. recce/data/_not-found/__next._head.txt +8 -0
  73. recce/data/_not-found/__next._index.txt +13 -0
  74. recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
  75. recce/data/_not-found/__next._not-found.txt +4 -0
  76. recce/data/_not-found/__next._tree.txt +6 -0
  77. recce/data/_not-found/index.html +2 -0
  78. recce/data/_not-found/index.txt +24 -0
  79. recce/data/auth_callback.html +1 -1
  80. recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
  81. recce/data/checks/__next._full.txt +39 -0
  82. recce/data/checks/__next._head.txt +8 -0
  83. recce/data/checks/__next._index.txt +14 -0
  84. recce/data/checks/__next._tree.txt +8 -0
  85. recce/data/checks/__next.checks.__PAGE__.txt +10 -0
  86. recce/data/checks/__next.checks.txt +4 -0
  87. recce/data/checks/index.html +2 -0
  88. recce/data/checks/index.txt +39 -0
  89. recce/data/index.html +2 -27
  90. recce/data/index.txt +32 -8
  91. recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
  92. recce/data/lineage/__next._full.txt +39 -0
  93. recce/data/lineage/__next._head.txt +8 -0
  94. recce/data/lineage/__next._index.txt +14 -0
  95. recce/data/lineage/__next._tree.txt +8 -0
  96. recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
  97. recce/data/lineage/__next.lineage.txt +4 -0
  98. recce/data/lineage/index.html +2 -0
  99. recce/data/lineage/index.txt +39 -0
  100. recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
  101. recce/data/query/__next._full.txt +37 -0
  102. recce/data/query/__next._head.txt +8 -0
  103. recce/data/query/__next._index.txt +14 -0
  104. recce/data/query/__next._tree.txt +8 -0
  105. recce/data/query/__next.query.__PAGE__.txt +9 -0
  106. recce/data/query/__next.query.txt +4 -0
  107. recce/data/query/index.html +2 -0
  108. recce/data/query/index.txt +37 -0
  109. recce/event/CONFIG.bak +1 -0
  110. recce/event/__init__.py +9 -8
  111. recce/event/collector.py +6 -2
  112. recce/event/track.py +10 -0
  113. recce/github.py +1 -1
  114. recce/mcp_server.py +725 -0
  115. recce/models/check.py +433 -15
  116. recce/models/types.py +61 -2
  117. recce/pull_request.py +1 -1
  118. recce/run.py +37 -17
  119. recce/server.py +216 -21
  120. recce/state/__init__.py +31 -0
  121. recce/state/cloud.py +644 -0
  122. recce/state/const.py +26 -0
  123. recce/state/local.py +56 -0
  124. recce/state/state.py +119 -0
  125. recce/state/state_loader.py +174 -0
  126. recce/summary.py +25 -3
  127. recce/tasks/dataframe.py +63 -1
  128. recce/tasks/query.py +40 -3
  129. recce/tasks/rowcount.py +4 -1
  130. recce/tasks/schema.py +4 -1
  131. recce/tasks/utils.py +147 -0
  132. recce/tasks/valuediff.py +85 -57
  133. recce/util/api_token.py +11 -2
  134. recce/util/breaking.py +10 -1
  135. recce/util/cll.py +1 -2
  136. recce/util/cloud/__init__.py +15 -0
  137. recce/util/cloud/base.py +115 -0
  138. recce/util/cloud/check_events.py +190 -0
  139. recce/util/cloud/checks.py +242 -0
  140. recce/util/io.py +2 -2
  141. recce/util/lineage.py +19 -18
  142. recce/util/perf_tracking.py +85 -0
  143. recce/util/recce_cloud.py +254 -5
  144. recce/util/startup_perf.py +121 -0
  145. recce/yaml/__init__.py +2 -2
  146. {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/METADATA +91 -71
  147. recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
  148. {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
  149. recce/data/_next/static/abCX3x3UoIdRLEDWxx4xd/_buildManifest.js +0 -1
  150. recce/data/_next/static/chunks/181-acc61ddada3bc0ca.js +0 -43
  151. recce/data/_next/static/chunks/1bff33f1-1ef85cf5e658a751.js +0 -1
  152. recce/data/_next/static/chunks/217-879a84d70f7a907c.js +0 -2
  153. recce/data/_next/static/chunks/29e3cc0d-60045b2e47aa3916.js +0 -1
  154. recce/data/_next/static/chunks/36e1c10d-8e7be4a6c1f6ab2d.js +0 -1
  155. recce/data/_next/static/chunks/3998a672-03adacad07b346ac.js +0 -1
  156. recce/data/_next/static/chunks/3a92ee20-1081c360214f9602.js +0 -1
  157. recce/data/_next/static/chunks/42-cd3c06533f5fd47c.js +0 -9
  158. recce/data/_next/static/chunks/450c323b-fd94e7ffaa4a5efa.js +0 -1
  159. recce/data/_next/static/chunks/47d8844f-929aed9b1c73a905.js +0 -1
  160. recce/data/_next/static/chunks/608-3b079b544e5d5f5e.js +0 -15
  161. recce/data/_next/static/chunks/6dc81886-adbfa45836061d79.js +0 -1
  162. recce/data/_next/static/chunks/7a8a3e83-edf6dc64b5d5f0a5.js +0 -1
  163. recce/data/_next/static/chunks/7f27ae6c-d5f0438edd5c2a5b.js +0 -1
  164. recce/data/_next/static/chunks/86730205-cfb14e3f051bab35.js +0 -1
  165. recce/data/_next/static/chunks/8d700b6a.8bb140898499c512.js +0 -1
  166. recce/data/_next/static/chunks/92-607cd1af83c41f43.js +0 -1
  167. recce/data/_next/static/chunks/9746af58-a42b7d169cacadf0.js +0 -1
  168. recce/data/_next/static/chunks/a30376cd-de84559016d7e133.js +0 -1
  169. recce/data/_next/static/chunks/app/_not-found/page-01ed58b7f971d311.js +0 -1
  170. recce/data/_next/static/chunks/app/layout-177a410a97e0d018.js +0 -1
  171. recce/data/_next/static/chunks/app/page-da6e046a8235dbfc.js +0 -1
  172. recce/data/_next/static/chunks/b63b1b3f-4282bdcf459e075c.js +0 -1
  173. recce/data/_next/static/chunks/bbda5537-9ec25eb1dd62348a.js +0 -1
  174. recce/data/_next/static/chunks/c132bf7d-08cb668a789d6afd.js +0 -1
  175. recce/data/_next/static/chunks/ce84277d-2e5d1d46910cf052.js +0 -1
  176. recce/data/_next/static/chunks/febdd86e-c6b525341634b860.js +0 -54
  177. recce/data/_next/static/chunks/fee69bc6-2dbccaf9b90474e6.js +0 -1
  178. recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
  179. recce/data/_next/static/chunks/main-app-39061b0166c47f55.js +0 -1
  180. recce/data/_next/static/chunks/main-b5b3ae20a1405261.js +0 -1
  181. recce/data/_next/static/chunks/pages/_app-437c455677d62394.js +0 -1
  182. recce/data/_next/static/chunks/pages/_error-e7650df18ca04bde.js +0 -1
  183. recce/data/_next/static/chunks/webpack-7b49d5ba7e3a434d.js +0 -1
  184. recce/data/_next/static/css/17a96168e3a9db13.css +0 -1
  185. recce/data/_next/static/css/1b121dc4d36aeb4d.css +0 -3
  186. recce/data/_next/static/css/35c6679a098e1e34.css +0 -1
  187. recce/data/_next/static/css/951e2e0eea2d4a5b.css +0 -14
  188. recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
  189. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
  190. recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
  191. recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
  192. recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
  193. recce/data/_next/static/media/reload-image.79aabb7d.svg +0 -4
  194. recce/state.py +0 -786
  195. recce_nightly-1.10.0.20250625.dist-info/RECORD +0 -154
  196. recce_nightly-1.10.0.20250625.dist-info/top_level.txt +0 -2
  197. tests/__init__.py +0 -0
  198. tests/adapter/__init__.py +0 -0
  199. tests/adapter/dbt_adapter/__init__.py +0 -0
  200. tests/adapter/dbt_adapter/conftest.py +0 -17
  201. tests/adapter/dbt_adapter/dbt_test_helper.py +0 -298
  202. tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -25
  203. tests/adapter/dbt_adapter/test_dbt_cll.py +0 -384
  204. tests/adapter/dbt_adapter/test_selector.py +0 -202
  205. tests/tasks/__init__.py +0 -0
  206. tests/tasks/conftest.py +0 -4
  207. tests/tasks/test_histogram.py +0 -129
  208. tests/tasks/test_lineage.py +0 -55
  209. tests/tasks/test_preset_checks.py +0 -64
  210. tests/tasks/test_profile.py +0 -397
  211. tests/tasks/test_query.py +0 -151
  212. tests/tasks/test_row_count.py +0 -135
  213. tests/tasks/test_schema.py +0 -122
  214. tests/tasks/test_top_k.py +0 -77
  215. tests/tasks/test_valuediff.py +0 -85
  216. tests/test_cli.py +0 -133
  217. tests/test_config.py +0 -43
  218. tests/test_connect_to_cloud.py +0 -82
  219. tests/test_core.py +0 -29
  220. tests/test_dbt.py +0 -36
  221. tests/test_pull_request.py +0 -130
  222. tests/test_server.py +0 -104
  223. tests/test_state.py +0 -134
  224. tests/test_summary.py +0 -65
  225. /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
  226. /recce/data/_next/static/media/{montserrat-cyrillic-ext-800-normal.e6e0d8d0.woff → montserrat-cyrillic-ext-800-normal.a4fa76b5.woff} +0 -0
  227. /recce/data/_next/static/{abCX3x3UoIdRLEDWxx4xd → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
  228. {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
  229. {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,190 @@
1
+ """
2
+ Recce Cloud API client for check event operations.
3
+
4
+ This module provides methods for managing check events (timeline/conversation) in Recce Cloud,
5
+ including CRUD operations for comments and retrieving state change events.
6
+ """
7
+
8
+ from typing import Dict, List
9
+
10
+ from recce.util.cloud.base import CloudBase
11
+
12
+
13
+ class CheckEventsCloud(CloudBase):
14
+ """
15
+ Client for Recce Cloud check event operations.
16
+
17
+ Provides methods to list, create, update, and delete check events
18
+ (comments and state changes) within Recce Cloud sessions.
19
+
20
+ Examples:
21
+ >>> client = CheckEventsCloud(token="your-api-token")
22
+ >>> events = client.list_events(
23
+ ... org_id="org123",
24
+ ... project_id="proj456",
25
+ ... session_id="sess789",
26
+ ... check_id="check001"
27
+ ... )
28
+ """
29
+
30
+ def _build_events_url(self, org_id: str, project_id: str, session_id: str, check_id: str) -> str:
31
+ """Build the base URL for check events endpoints."""
32
+ return f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}/events"
33
+
34
+ def list_events(self, org_id: str, project_id: str, session_id: str, check_id: str) -> List[Dict]:
35
+ """
36
+ List all events for a check in chronological order.
37
+
38
+ Args:
39
+ org_id: Organization ID
40
+ project_id: Project ID
41
+ session_id: Session ID
42
+ check_id: Check ID
43
+
44
+ Returns:
45
+ List of event dictionaries
46
+
47
+ Raises:
48
+ RecceCloudException: If the request fails
49
+
50
+ Example:
51
+ >>> events = client.list_events("org123", "proj456", "sess789", "check001")
52
+ >>> for event in events:
53
+ ... print(f"{event['event_type']}: {event['content']}")
54
+ """
55
+ api_url = self._build_events_url(org_id, project_id, session_id, check_id)
56
+ query_params = {"include_deleted": True}
57
+ response = self._request("GET", api_url, params=query_params)
58
+
59
+ self._raise_for_status(
60
+ response,
61
+ "Failed to list check events from Recce Cloud.",
62
+ )
63
+
64
+ data = response.json()
65
+ # Response is wrapped: {"events": [...]}
66
+ return data.get("events", [])
67
+
68
+ def get_event(self, org_id: str, project_id: str, session_id: str, check_id: str, event_id: str) -> Dict:
69
+ """
70
+ Get a specific event by ID.
71
+
72
+ Args:
73
+ org_id: Organization ID
74
+ project_id: Project ID
75
+ session_id: Session ID
76
+ check_id: Check ID
77
+ event_id: Event ID
78
+
79
+ Returns:
80
+ Event dictionary
81
+
82
+ Raises:
83
+ RecceCloudException: If the request fails or event not found
84
+ """
85
+ api_url = f"{self._build_events_url(org_id, project_id, session_id, check_id)}/{event_id}"
86
+ response = self._request("GET", api_url)
87
+
88
+ self._raise_for_status(
89
+ response,
90
+ f"Failed to get check event {event_id} from Recce Cloud.",
91
+ )
92
+
93
+ data = response.json()
94
+ # Response is wrapped: {"event": {...}}
95
+ return data.get("event", {})
96
+
97
+ def create_comment(self, org_id: str, project_id: str, session_id: str, check_id: str, content: str) -> Dict:
98
+ """
99
+ Create a new comment on a check.
100
+
101
+ Args:
102
+ org_id: Organization ID
103
+ project_id: Project ID
104
+ session_id: Session ID
105
+ check_id: Check ID
106
+ content: Comment content (plain text or markdown)
107
+
108
+ Returns:
109
+ Created event dictionary
110
+
111
+ Raises:
112
+ RecceCloudException: If the request fails
113
+
114
+ Example:
115
+ >>> event = client.create_comment(
116
+ ... "org123", "proj456", "sess789", "check001",
117
+ ... "This looks good to me!"
118
+ ... )
119
+ >>> print(f"Created comment with ID: {event['id']}")
120
+ """
121
+ api_url = self._build_events_url(org_id, project_id, session_id, check_id)
122
+ response = self._request("POST", api_url, json={"content": content})
123
+
124
+ self._raise_for_status(
125
+ response,
126
+ "Failed to create comment in Recce Cloud.",
127
+ )
128
+
129
+ data = response.json()
130
+ # Response is wrapped: {"event": {...}}
131
+ return data.get("event", {})
132
+
133
+ def update_comment(
134
+ self, org_id: str, project_id: str, session_id: str, check_id: str, event_id: str, content: str
135
+ ) -> Dict:
136
+ """
137
+ Update an existing comment.
138
+
139
+ Only the author or an admin can update a comment.
140
+
141
+ Args:
142
+ org_id: Organization ID
143
+ project_id: Project ID
144
+ session_id: Session ID
145
+ check_id: Check ID
146
+ event_id: Event ID of the comment to update
147
+ content: New comment content
148
+
149
+ Returns:
150
+ Updated event dictionary
151
+
152
+ Raises:
153
+ RecceCloudException: If the request fails or user is not authorized
154
+ """
155
+ api_url = f"{self._build_events_url(org_id, project_id, session_id, check_id)}/{event_id}"
156
+ response = self._request("PATCH", api_url, json={"content": content})
157
+
158
+ self._raise_for_status(
159
+ response,
160
+ f"Failed to update comment {event_id} in Recce Cloud.",
161
+ )
162
+
163
+ data = response.json()
164
+ # Response is wrapped: {"event": {...}}
165
+ return data.get("event", {})
166
+
167
+ def delete_comment(self, org_id: str, project_id: str, session_id: str, check_id: str, event_id: str) -> None:
168
+ """
169
+ Delete a comment (soft delete).
170
+
171
+ Only the author or an admin can delete a comment.
172
+ The comment will be marked as deleted but remain in the timeline.
173
+
174
+ Args:
175
+ org_id: Organization ID
176
+ project_id: Project ID
177
+ session_id: Session ID
178
+ check_id: Check ID
179
+ event_id: Event ID of the comment to delete
180
+
181
+ Raises:
182
+ RecceCloudException: If the request fails or user is not authorized
183
+ """
184
+ api_url = f"{self._build_events_url(org_id, project_id, session_id, check_id)}/{event_id}"
185
+ response = self._request("DELETE", api_url)
186
+
187
+ self._raise_for_status(
188
+ response,
189
+ f"Failed to delete comment {event_id} in Recce Cloud.",
190
+ )
@@ -0,0 +1,242 @@
1
+ """
2
+ Recce Cloud API client for check operations.
3
+
4
+ This module provides methods for managing checks (validation operations) in Recce Cloud,
5
+ including CRUD operations for checks within sessions.
6
+ """
7
+
8
+ from typing import Dict, List
9
+
10
+ from recce.util.cloud.base import CloudBase
11
+
12
+
13
+ class ChecksCloud(CloudBase):
14
+ """
15
+ Client for Recce Cloud check operations.
16
+
17
+ Provides methods to list, create, retrieve, update, and delete checks
18
+ within Recce Cloud sessions.
19
+
20
+ Examples:
21
+ >>> client = ChecksCloud(token="your-api-token")
22
+ >>> checks = client.list_checks(org_id="org123", project_id="proj456", session_id="sess789")
23
+ >>> check = client.get_check(org_id="org123", project_id="proj456",
24
+ ... session_id="sess789", check_id="check001")
25
+ """
26
+
27
+ def list_checks(self, org_id: str, project_id: str, session_id: str) -> List[Dict]:
28
+ """
29
+ List all checks in a session.
30
+
31
+ Args:
32
+ org_id: Organization ID
33
+ project_id: Project ID
34
+ session_id: Session ID
35
+
36
+ Returns:
37
+ List of check dictionaries
38
+
39
+ Raises:
40
+ RecceCloudException: If the request fails
41
+
42
+ Example:
43
+ >>> checks = client.list_checks("org123", "proj456", "sess789")
44
+ >>> print(f"Found {len(checks)} checks")
45
+ """
46
+ api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks"
47
+ response = self._request("GET", api_url)
48
+
49
+ self._raise_for_status(
50
+ response,
51
+ "Failed to list checks from Recce Cloud.",
52
+ )
53
+
54
+ data = response.json()
55
+ return data.get("checks", [])
56
+
57
+ def create_check(
58
+ self,
59
+ org_id: str,
60
+ project_id: str,
61
+ session_id: str,
62
+ check_data: Dict,
63
+ ) -> Dict:
64
+ """
65
+ Create a new check in a session.
66
+
67
+ Args:
68
+ org_id: Organization ID
69
+ project_id: Project ID
70
+ session_id: Session ID
71
+ check_data: Check data to create (should include check type, params, etc.)
72
+
73
+ Returns:
74
+ Created check dictionary
75
+
76
+ Raises:
77
+ RecceCloudException: If the request fails
78
+
79
+ Example:
80
+ >>> check_data = {
81
+ ... "name": "Schema Check",
82
+ ... "type": "schema_diff",
83
+ ... "params": {"model": "customers"}
84
+ ... }
85
+ >>> check = client.create_check("org123", "proj456", "sess789", check_data)
86
+ >>> print(f"Created check with ID: {check['id']}")
87
+ """
88
+ api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks"
89
+ response = self._request("POST", api_url, json=check_data)
90
+
91
+ self._raise_for_status(
92
+ response,
93
+ "Failed to create check in Recce Cloud.",
94
+ )
95
+ data = response.json()
96
+ return data.get("check")
97
+
98
+ def get_check(
99
+ self,
100
+ org_id: str,
101
+ project_id: str,
102
+ session_id: str,
103
+ check_id: str,
104
+ ) -> Dict:
105
+ """
106
+ Get a specific check by ID.
107
+
108
+ Args:
109
+ org_id: Organization ID
110
+ project_id: Project ID
111
+ session_id: Session ID
112
+ check_id: Check ID
113
+
114
+ Returns:
115
+ Check dictionary
116
+
117
+ Raises:
118
+ RecceCloudException: If the request fails or check not found
119
+
120
+ Example:
121
+ >>> check = client.get_check("org123", "proj456", "sess789", "check001")
122
+ >>> print(f"Check name: {check['name']}")
123
+ >>> print(f"Check status: {check['status']}")
124
+ """
125
+ api_url = (
126
+ f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}"
127
+ )
128
+ response = self._request("GET", api_url)
129
+
130
+ self._raise_for_status(
131
+ response,
132
+ f"Failed to get check {check_id} from Recce Cloud.",
133
+ )
134
+
135
+ data = response.json()
136
+ return data.get("check", {})
137
+
138
+ def update_check(
139
+ self,
140
+ org_id: str,
141
+ project_id: str,
142
+ session_id: str,
143
+ check_id: str,
144
+ check_data: Dict,
145
+ ) -> Dict:
146
+ """
147
+ Update an existing check.
148
+
149
+ Args:
150
+ org_id: Organization ID
151
+ project_id: Project ID
152
+ session_id: Session ID
153
+ check_id: Check ID
154
+ check_data: Updated check data (partial updates supported)
155
+
156
+ Returns:
157
+ Updated check dictionary
158
+
159
+ Raises:
160
+ RecceCloudException: If the request fails
161
+
162
+ Example:
163
+ >>> update_data = {
164
+ ... "status": "approved",
165
+ ... "notes": "Validated successfully"
166
+ ... }
167
+ >>> check = client.update_check("org123", "proj456", "sess789", "check001", update_data)
168
+ >>> print(f"Updated check status: {check['status']}")
169
+ """
170
+ api_url = (
171
+ f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}"
172
+ )
173
+ response = self._request("PATCH", api_url, json=check_data)
174
+
175
+ self._raise_for_status(
176
+ response,
177
+ f"Failed to update check {check_id} in Recce Cloud.",
178
+ )
179
+
180
+ data = response.json()
181
+ return data.get("check", {})
182
+
183
+ def delete_check(
184
+ self,
185
+ org_id: str,
186
+ project_id: str,
187
+ session_id: str,
188
+ check_id: str,
189
+ ) -> None:
190
+ """
191
+ Delete a check.
192
+
193
+ Args:
194
+ org_id: Organization ID
195
+ project_id: Project ID
196
+ session_id: Session ID
197
+ check_id: Check ID
198
+
199
+ Raises:
200
+ RecceCloudException: If the request fails
201
+
202
+ Example:
203
+ >>> client.delete_check("org123", "proj456", "sess789", "check001")
204
+ >>> print("Check deleted successfully")
205
+ """
206
+ api_url = (
207
+ f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}"
208
+ )
209
+ response = self._request("DELETE", api_url)
210
+
211
+ # DELETE typically returns 204 No Content on success
212
+ if response.status_code not in (200, 204):
213
+ self._raise_for_status(
214
+ response,
215
+ f"Failed to delete check {check_id} from Recce Cloud.",
216
+ )
217
+
218
+ def create_preset_check(self, org_id: str, project_id: str, check_data: Dict):
219
+ """
220
+ Create a preset check from an existing check.
221
+
222
+ Args:
223
+ org_id: Organization ID
224
+ project_id: Project ID
225
+ check_data: Check data including name, description, type, params, view_options, and order_index
226
+
227
+ Returns:
228
+ Created preset check dictionary
229
+
230
+ Raises:
231
+ RecceCloudException: If the request fails
232
+ """
233
+ api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/preset-checks"
234
+ response = self._request("POST", api_url, json=check_data)
235
+
236
+ self._raise_for_status(
237
+ response,
238
+ "Failed to create preset check in Recce Cloud.",
239
+ )
240
+
241
+ data = response.json()
242
+ return data.get("presetCheck", {})
recce/util/io.py CHANGED
@@ -37,12 +37,12 @@ class AbstractFileIO(metaclass=ABCMeta):
37
37
  class FileIO(AbstractFileIO, ABC):
38
38
  @staticmethod
39
39
  def write(path: str, data: str, **kwargs):
40
- with open(path, "w") as f:
40
+ with open(path, "w", encoding="utf-8") as f:
41
41
  f.write(data)
42
42
 
43
43
  @staticmethod
44
44
  def read(path: str, **kwargs) -> str:
45
- with open(path, "r") as f:
45
+ with open(path, "r", encoding="utf-8") as f:
46
46
  return f.read()
47
47
 
48
48
 
recce/util/lineage.py CHANGED
@@ -7,19 +7,17 @@ def find_upstream(node_ids: Iterable, parent_map):
7
7
  visited = set()
8
8
  upstream = set()
9
9
 
10
- def dfs(current):
10
+ stack = list(node_ids)
11
+ while stack:
12
+ current = stack.pop()
11
13
  if current in visited:
12
- return
14
+ continue
13
15
  visited.add(current)
14
-
15
16
  parents = parent_map.get(current, [])
16
17
  for parent in parents:
17
- upstream.add(parent)
18
- dfs(parent)
19
-
20
- for node_id in node_ids:
21
- dfs(node_id)
22
-
18
+ if parent not in upstream:
19
+ upstream.add(parent)
20
+ stack.append(parent)
23
21
  return upstream
24
22
 
25
23
 
@@ -27,19 +25,17 @@ def find_downstream(node_ids: Iterable, child_map):
27
25
  visited = set()
28
26
  downstream = set()
29
27
 
30
- def dfs(current):
28
+ stack = list(node_ids)
29
+ while stack:
30
+ current = stack.pop()
31
31
  if current in visited:
32
- return
32
+ continue
33
33
  visited.add(current)
34
-
35
34
  children = child_map.get(current, [])
36
35
  for child in children:
37
- downstream.add(child)
38
- dfs(child)
39
-
40
- for node_id in node_ids:
41
- dfs(node_id)
42
-
36
+ if child not in downstream:
37
+ downstream.add(child)
38
+ stack.append(child)
43
39
  return downstream
44
40
 
45
41
 
@@ -80,3 +76,8 @@ def filter_dependency_maps(
80
76
  c_map[node_id] = {c for c in children if c in relevant_ids}
81
77
 
82
78
  return p_map, c_map
79
+
80
+
81
+ def build_column_key(node_id: str, column_name: str) -> str:
82
+ """Build a unique column key from node name and column name."""
83
+ return f"{node_id}_{column_name}"
@@ -0,0 +1,85 @@
1
+ import time
2
+ from dataclasses import dataclass
3
+
4
+
5
+ @dataclass
6
+ class LineagePerfTracker:
7
+ lineage_start = None
8
+ lineage_elapsed = None
9
+ column_lineage_start = None
10
+ column_lineage_elapsed = None
11
+
12
+ total_nodes = None
13
+ init_nodes = None
14
+ cll_nodes = 0
15
+ change_analysis_nodes = 0
16
+ anchor_nodes = None
17
+
18
+ params = None
19
+
20
+ def start_lineage(self):
21
+ self.lineage_start = time.perf_counter_ns()
22
+
23
+ def end_lineage(self):
24
+ if self.lineage_start is None:
25
+ return
26
+ self.lineage_elapsed = (time.perf_counter_ns() - self.lineage_start) / 1000000
27
+
28
+ def start_column_lineage(self):
29
+ self.column_lineage_start = time.perf_counter_ns()
30
+
31
+ def end_column_lineage(self):
32
+ if self.column_lineage_start is None:
33
+ return
34
+ self.column_lineage_elapsed = (time.perf_counter_ns() - self.column_lineage_start) / 1000000
35
+
36
+ def set_total_nodes(self, total_nodes):
37
+ self.total_nodes = total_nodes
38
+
39
+ def set_init_nodes(self, init_nodes):
40
+ self.init_nodes = init_nodes
41
+
42
+ def set_anchor_nodes(self, anchor_nodes):
43
+ self.anchor_nodes = anchor_nodes
44
+
45
+ def increment_cll_nodes(self):
46
+ self.cll_nodes += 1
47
+
48
+ def increment_change_analysis_nodes(self):
49
+ self.change_analysis_nodes += 1
50
+
51
+ def set_params(self, has_node, has_column, change_analysis, no_cll, no_upstream, no_downstream):
52
+ self.params = {
53
+ "has_node": has_node,
54
+ "has_column": has_column,
55
+ "change_analysis": change_analysis,
56
+ "no_cll": no_cll,
57
+ "no_upstream": no_upstream,
58
+ "no_downstream": no_downstream,
59
+ }
60
+
61
+ def to_dict(self):
62
+ return {
63
+ "lineage_elapsed_ms": self.lineage_elapsed,
64
+ "column_lineage_elapsed_ms": self.column_lineage_elapsed,
65
+ "total_nodes": self.total_nodes,
66
+ "init_nodes": self.init_nodes,
67
+ "cll_nodes": self.cll_nodes,
68
+ "change_analysis_nodes": self.change_analysis_nodes,
69
+ "anchor_nodes": self.anchor_nodes,
70
+ "params": self.params,
71
+ }
72
+
73
+ def reset(self):
74
+ self.lineage_start = None
75
+ self.lineage_elapsed = None
76
+ self.column_lineage_start = None
77
+ self.column_lineage_elapsed = None
78
+
79
+ self.total_nodes = None
80
+ self.init_nodes = None
81
+ self.change_analysis_nodes = 0
82
+ self.cll_nodes = 0
83
+ self.anchor_nodes = 0
84
+
85
+ self.params = None