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.
- recce/VERSION +1 -1
- recce/__init__.py +5 -0
- recce/adapter/dbt_adapter/__init__.py +343 -245
- recce/apis/check_api.py +20 -14
- recce/apis/check_events_api.py +353 -0
- recce/apis/check_func.py +5 -5
- recce/apis/run_func.py +32 -3
- recce/artifact.py +76 -3
- recce/cli.py +705 -82
- recce/config.py +2 -2
- recce/connect_to_cloud.py +1 -1
- recce/core.py +3 -3
- recce/data/404/index.html +2 -0
- recce/data/404.html +2 -22
- 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.bd5c9f50.woff → 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-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/{montserrat-latin-800-normal.fc315020.woff → 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.2e5381b2.woff → montserrat-latin-ext-800-normal.b671449b.woff} +0 -0
- recce/data/_next/static/media/{montserrat-vietnamese-800-normal.20c545e6.woff → 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 +1 -1
- 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/index.html +2 -27
- recce/data/index.txt +32 -8
- 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/event/CONFIG.bak +1 -0
- recce/event/__init__.py +9 -8
- recce/event/collector.py +6 -2
- recce/event/track.py +10 -0
- recce/github.py +1 -1
- recce/mcp_server.py +725 -0
- recce/models/check.py +433 -15
- recce/models/types.py +61 -2
- recce/pull_request.py +1 -1
- recce/run.py +37 -17
- recce/server.py +216 -21
- 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 +25 -3
- recce/tasks/dataframe.py +63 -1
- recce/tasks/query.py +40 -3
- recce/tasks/rowcount.py +4 -1
- recce/tasks/schema.py +4 -1
- recce/tasks/utils.py +147 -0
- recce/tasks/valuediff.py +85 -57
- recce/util/api_token.py +11 -2
- recce/util/breaking.py +10 -1
- recce/util/cll.py +1 -2
- 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 +2 -2
- recce/util/lineage.py +19 -18
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +254 -5
- recce/util/startup_perf.py +121 -0
- recce/yaml/__init__.py +2 -2
- {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/METADATA +91 -71
- recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
- {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
- recce/data/_next/static/abCX3x3UoIdRLEDWxx4xd/_buildManifest.js +0 -1
- recce/data/_next/static/chunks/181-acc61ddada3bc0ca.js +0 -43
- recce/data/_next/static/chunks/1bff33f1-1ef85cf5e658a751.js +0 -1
- recce/data/_next/static/chunks/217-879a84d70f7a907c.js +0 -2
- recce/data/_next/static/chunks/29e3cc0d-60045b2e47aa3916.js +0 -1
- recce/data/_next/static/chunks/36e1c10d-8e7be4a6c1f6ab2d.js +0 -1
- recce/data/_next/static/chunks/3998a672-03adacad07b346ac.js +0 -1
- recce/data/_next/static/chunks/3a92ee20-1081c360214f9602.js +0 -1
- recce/data/_next/static/chunks/42-cd3c06533f5fd47c.js +0 -9
- recce/data/_next/static/chunks/450c323b-fd94e7ffaa4a5efa.js +0 -1
- recce/data/_next/static/chunks/47d8844f-929aed9b1c73a905.js +0 -1
- recce/data/_next/static/chunks/608-3b079b544e5d5f5e.js +0 -15
- recce/data/_next/static/chunks/6dc81886-adbfa45836061d79.js +0 -1
- recce/data/_next/static/chunks/7a8a3e83-edf6dc64b5d5f0a5.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-d5f0438edd5c2a5b.js +0 -1
- recce/data/_next/static/chunks/86730205-cfb14e3f051bab35.js +0 -1
- recce/data/_next/static/chunks/8d700b6a.8bb140898499c512.js +0 -1
- recce/data/_next/static/chunks/92-607cd1af83c41f43.js +0 -1
- recce/data/_next/static/chunks/9746af58-a42b7d169cacadf0.js +0 -1
- recce/data/_next/static/chunks/a30376cd-de84559016d7e133.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-01ed58b7f971d311.js +0 -1
- recce/data/_next/static/chunks/app/layout-177a410a97e0d018.js +0 -1
- recce/data/_next/static/chunks/app/page-da6e046a8235dbfc.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-4282bdcf459e075c.js +0 -1
- recce/data/_next/static/chunks/bbda5537-9ec25eb1dd62348a.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-08cb668a789d6afd.js +0 -1
- recce/data/_next/static/chunks/ce84277d-2e5d1d46910cf052.js +0 -1
- recce/data/_next/static/chunks/febdd86e-c6b525341634b860.js +0 -54
- recce/data/_next/static/chunks/fee69bc6-2dbccaf9b90474e6.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-app-39061b0166c47f55.js +0 -1
- recce/data/_next/static/chunks/main-b5b3ae20a1405261.js +0 -1
- recce/data/_next/static/chunks/pages/_app-437c455677d62394.js +0 -1
- recce/data/_next/static/chunks/pages/_error-e7650df18ca04bde.js +0 -1
- recce/data/_next/static/chunks/webpack-7b49d5ba7e3a434d.js +0 -1
- recce/data/_next/static/css/17a96168e3a9db13.css +0 -1
- recce/data/_next/static/css/1b121dc4d36aeb4d.css +0 -3
- recce/data/_next/static/css/35c6679a098e1e34.css +0 -1
- recce/data/_next/static/css/951e2e0eea2d4a5b.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +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-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/data/_next/static/media/reload-image.79aabb7d.svg +0 -4
- recce/state.py +0 -786
- recce_nightly-1.10.0.20250625.dist-info/RECORD +0 -154
- recce_nightly-1.10.0.20250625.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 -17
- tests/adapter/dbt_adapter/dbt_test_helper.py +0 -298
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -25
- tests/adapter/dbt_adapter/test_dbt_cll.py +0 -384
- tests/adapter/dbt_adapter/test_selector.py +0 -202
- tests/tasks/__init__.py +0 -0
- tests/tasks/conftest.py +0 -4
- tests/tasks/test_histogram.py +0 -129
- tests/tasks/test_lineage.py +0 -55
- tests/tasks/test_preset_checks.py +0 -64
- tests/tasks/test_profile.py +0 -397
- tests/tasks/test_query.py +0 -151
- tests/tasks/test_row_count.py +0 -135
- tests/tasks/test_schema.py +0 -122
- tests/tasks/test_top_k.py +0 -77
- tests/tasks/test_valuediff.py +0 -85
- tests/test_cli.py +0 -133
- tests/test_config.py +0 -43
- tests/test_connect_to_cloud.py +0 -82
- tests/test_core.py +0 -29
- tests/test_dbt.py +0 -36
- tests/test_pull_request.py +0 -130
- tests/test_server.py +0 -104
- tests/test_state.py +0 -134
- tests/test_summary.py +0 -65
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- /recce/data/_next/static/media/{montserrat-cyrillic-ext-800-normal.e6e0d8d0.woff → montserrat-cyrillic-ext-800-normal.a4fa76b5.woff} +0 -0
- /recce/data/_next/static/{abCX3x3UoIdRLEDWxx4xd → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
- {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
10
|
+
stack = list(node_ids)
|
|
11
|
+
while stack:
|
|
12
|
+
current = stack.pop()
|
|
11
13
|
if current in visited:
|
|
12
|
-
|
|
14
|
+
continue
|
|
13
15
|
visited.add(current)
|
|
14
|
-
|
|
15
16
|
parents = parent_map.get(current, [])
|
|
16
17
|
for parent in parents:
|
|
17
|
-
upstream
|
|
18
|
-
|
|
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
|
-
|
|
28
|
+
stack = list(node_ids)
|
|
29
|
+
while stack:
|
|
30
|
+
current = stack.pop()
|
|
31
31
|
if current in visited:
|
|
32
|
-
|
|
32
|
+
continue
|
|
33
33
|
visited.add(current)
|
|
34
|
-
|
|
35
34
|
children = child_map.get(current, [])
|
|
36
35
|
for child in children:
|
|
37
|
-
downstream
|
|
38
|
-
|
|
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
|