recce-nightly 0.62.0.20250417__py3-none-any.whl → 1.30.0.20251221__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +27 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +845 -461
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +59 -42
- recce/apis/check_events_api.py +353 -0
- recce/apis/check_func.py +41 -35
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +64 -25
- recce/artifact.py +119 -51
- recce/cli.py +1301 -324
- recce/config.py +43 -34
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404/index.html +2 -0
- recce/data/404.html +2 -1
- recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
- recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
- recce/data/__next.__PAGE__.txt +6 -0
- recce/data/__next._full.txt +32 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +14 -0
- recce/data/__next._tree.txt +8 -0
- recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
- recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
- recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
- recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
- recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
- recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
- recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
- recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
- recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
- recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
- recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
- recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
- recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
- recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
- recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
- recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
- recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
- recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
- recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
- recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
- recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
- recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
- recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
- recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
- recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
- recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
- recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
- recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
- recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
- recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
- recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
- recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
- recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
- recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
- recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
- recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
- recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
- recce/data/_not-found/__next._full.txt +24 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +13 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +6 -0
- recce/data/_not-found/index.html +2 -0
- recce/data/_not-found/index.txt +24 -0
- recce/data/auth_callback.html +68 -0
- recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/checks/__next._full.txt +39 -0
- recce/data/checks/__next._head.txt +8 -0
- recce/data/checks/__next._index.txt +14 -0
- recce/data/checks/__next._tree.txt +8 -0
- recce/data/checks/__next.checks.__PAGE__.txt +10 -0
- recce/data/checks/__next.checks.txt +4 -0
- recce/data/checks/index.html +2 -0
- recce/data/checks/index.txt +39 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +2 -27
- recce/data/index.txt +32 -7
- recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/lineage/__next._full.txt +39 -0
- recce/data/lineage/__next._head.txt +8 -0
- recce/data/lineage/__next._index.txt +14 -0
- recce/data/lineage/__next._tree.txt +8 -0
- recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
- recce/data/lineage/__next.lineage.txt +4 -0
- recce/data/lineage/index.html +2 -0
- recce/data/lineage/index.txt +39 -0
- recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/query/__next._full.txt +37 -0
- recce/data/query/__next._head.txt +8 -0
- recce/data/query/__next._index.txt +14 -0
- recce/data/query/__next._tree.txt +8 -0
- recce/data/query/__next.query.__PAGE__.txt +9 -0
- recce/data/query/__next.query.txt +4 -0
- recce/data/query/index.html +2 -0
- recce/data/query/index.txt +37 -0
- recce/diff.py +6 -12
- recce/event/CONFIG.bak +1 -0
- recce/event/__init__.py +86 -74
- recce/event/collector.py +33 -22
- recce/event/track.py +49 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/mcp_server.py +725 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +438 -21
- recce/models/run.py +1 -0
- recce/models/types.py +134 -28
- recce/pull_request.py +27 -25
- recce/run.py +179 -122
- recce/server.py +394 -104
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +644 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +196 -149
- recce/tasks/__init__.py +19 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +82 -18
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +152 -86
- recce/tasks/query.py +180 -89
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/utils.py +147 -0
- recce/tasks/valuediff.py +247 -155
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +105 -100
- recce/util/cll.py +274 -219
- recce/util/cloud/__init__.py +15 -0
- recce/util/cloud/base.py +115 -0
- recce/util/cloud/check_events.py +190 -0
- recce/util/cloud/checks.py +242 -0
- recce/util/io.py +22 -17
- recce/util/lineage.py +65 -16
- recce/util/logger.py +1 -1
- recce/util/onboarding_state.py +45 -0
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +347 -72
- recce/util/singleton.py +4 -4
- recce/util/startup_perf.py +121 -0
- recce/yaml/__init__.py +7 -10
- recce_nightly-1.30.0.20251221.dist-info/METADATA +195 -0
- recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
- recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
- recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
- recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
- recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
- recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
- recce/data/_next/static/chunks/500-e51c92a025a51234.js +0 -65
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
- recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
- recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
- recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
- recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
- recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
- recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
- recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
- recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
- recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
- recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/data/_next/static/qiyFlux77VkhxiceAJe_F/_buildManifest.js +0 -1
- recce/state.py +0 -753
- recce_nightly-0.62.0.20250417.dist-info/METADATA +0 -311
- recce_nightly-0.62.0.20250417.dist-info/RECORD +0 -139
- recce_nightly-0.62.0.20250417.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/conftest.py +0 -13
- tests/adapter/dbt_adapter/dbt_test_helper.py +0 -283
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -40
- tests/adapter/dbt_adapter/test_dbt_cll.py +0 -102
- tests/adapter/dbt_adapter/test_selector.py +0 -177
- tests/tasks/__init__.py +0 -0
- tests/tasks/conftest.py +0 -4
- tests/tasks/test_histogram.py +0 -137
- tests/tasks/test_lineage.py +0 -42
- tests/tasks/test_preset_checks.py +0 -50
- tests/tasks/test_profile.py +0 -73
- tests/tasks/test_query.py +0 -151
- tests/tasks/test_row_count.py +0 -116
- tests/tasks/test_schema.py +0 -99
- tests/tasks/test_top_k.py +0 -73
- tests/tasks/test_valuediff.py +0 -74
- tests/test_cli.py +0 -122
- tests/test_config.py +0 -45
- tests/test_core.py +0 -27
- tests/test_dbt.py +0 -36
- tests/test_pull_request.py +0 -130
- tests/test_server.py +0 -98
- tests/test_state.py +0 -123
- tests/test_summary.py +0 -57
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- /recce/data/_next/static/{qiyFlux77VkhxiceAJe_F → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE +0 -0
recce/event/collector.py
CHANGED
|
@@ -4,20 +4,19 @@ import platform
|
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
6
|
from contextlib import contextmanager
|
|
7
|
-
from datetime import datetime
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
8
|
from json import JSONDecodeError
|
|
9
|
-
from datetime import timezone
|
|
10
9
|
|
|
11
10
|
import portalocker
|
|
12
11
|
import requests
|
|
13
12
|
|
|
14
|
-
from recce import __version__, is_ci_env
|
|
13
|
+
from recce import __version__, is_ci_env, is_recce_cloud_instance
|
|
15
14
|
from recce.github import is_github_codespace
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class Collector:
|
|
19
18
|
def __init__(self):
|
|
20
|
-
self._api_endpoint =
|
|
19
|
+
self._api_endpoint = "https://api.amplitude.com/2/httpapi"
|
|
21
20
|
self._api_key = None
|
|
22
21
|
self._user_id = None
|
|
23
22
|
|
|
@@ -26,6 +25,7 @@ class Collector:
|
|
|
26
25
|
self._upload_threshold = 10
|
|
27
26
|
self._is_ci: bool = is_ci_env()
|
|
28
27
|
self._is_github_codespace: bool = is_github_codespace()
|
|
28
|
+
self._is_recce_cloud_instance: bool = is_recce_cloud_instance()
|
|
29
29
|
self._flush_timer = None
|
|
30
30
|
|
|
31
31
|
def schedule_flush(self):
|
|
@@ -35,6 +35,7 @@ class Collector:
|
|
|
35
35
|
|
|
36
36
|
# send async thread
|
|
37
37
|
import threading
|
|
38
|
+
|
|
38
39
|
if self._flush_timer:
|
|
39
40
|
try:
|
|
40
41
|
self._flush_timer.cancel()
|
|
@@ -59,11 +60,18 @@ class Collector:
|
|
|
59
60
|
self._unsend_events_file = unsend_events_file
|
|
60
61
|
self._check_required_files()
|
|
61
62
|
|
|
62
|
-
def _log_event(
|
|
63
|
+
def _log_event(
|
|
64
|
+
self,
|
|
65
|
+
user_id,
|
|
66
|
+
event_type,
|
|
67
|
+
created_at,
|
|
68
|
+
user_properties,
|
|
69
|
+
event_properties,
|
|
70
|
+
):
|
|
63
71
|
event = dict(
|
|
64
72
|
user_id=user_id,
|
|
65
73
|
event_type=event_type,
|
|
66
|
-
ip=
|
|
74
|
+
ip="$remote",
|
|
67
75
|
time=int(time.mktime(created_at.timetuple())),
|
|
68
76
|
user_properties=user_properties,
|
|
69
77
|
event_properties=event_properties,
|
|
@@ -91,7 +99,7 @@ class Collector:
|
|
|
91
99
|
else:
|
|
92
100
|
# Convert to UTC timezone
|
|
93
101
|
created_at = event_triggered_at.astimezone(timezone.utc)
|
|
94
|
-
python_version = f
|
|
102
|
+
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
95
103
|
|
|
96
104
|
# when the recce is running in automation use cases
|
|
97
105
|
# replace the user id with project_id to avoid so many unique user id
|
|
@@ -102,6 +110,7 @@ class Collector:
|
|
|
102
110
|
python_version=python_version,
|
|
103
111
|
is_ci=self._is_ci,
|
|
104
112
|
is_github_codespace=self._is_github_codespace,
|
|
113
|
+
is_recce_cloud_instance=self._is_recce_cloud_instance,
|
|
105
114
|
)
|
|
106
115
|
|
|
107
116
|
if user_properties is not None:
|
|
@@ -120,17 +129,18 @@ class Collector:
|
|
|
120
129
|
if not os.path.exists(user_home):
|
|
121
130
|
os.makedirs(user_home, exist_ok=True)
|
|
122
131
|
if not os.path.exists(self._unsend_events_file):
|
|
123
|
-
with portalocker.Lock(self._unsend_events_file,
|
|
124
|
-
f.write(json.dumps({
|
|
132
|
+
with portalocker.Lock(self._unsend_events_file, "w+", timeout=5) as f:
|
|
133
|
+
f.write(json.dumps({"unsend_events": []}))
|
|
125
134
|
|
|
126
135
|
def _is_full(self):
|
|
127
|
-
with portalocker.Lock(self._unsend_events_file,
|
|
136
|
+
with portalocker.Lock(self._unsend_events_file, "r+", timeout=5) as f:
|
|
128
137
|
o = json.loads(f.read())
|
|
129
|
-
return len(o.get(
|
|
138
|
+
return len(o.get("unsend_events", [])) >= self._upload_threshold
|
|
130
139
|
|
|
131
140
|
@contextmanager
|
|
132
141
|
def load_json(self):
|
|
133
|
-
with portalocker.Lock(self._unsend_events_file,
|
|
142
|
+
with portalocker.Lock(self._unsend_events_file, "r+", timeout=5) as f:
|
|
143
|
+
o = None
|
|
134
144
|
try:
|
|
135
145
|
o = json.loads(f.read())
|
|
136
146
|
yield o
|
|
@@ -140,15 +150,16 @@ class Collector:
|
|
|
140
150
|
finally:
|
|
141
151
|
f.seek(0)
|
|
142
152
|
f.truncate()
|
|
143
|
-
|
|
153
|
+
if o is not None:
|
|
154
|
+
f.write(json.dumps(o))
|
|
144
155
|
|
|
145
156
|
def send_events(self):
|
|
146
157
|
with self.load_json() as o:
|
|
147
158
|
payload = dict(
|
|
148
159
|
api_key=self._api_key,
|
|
149
|
-
events=o[
|
|
160
|
+
events=o["unsend_events"],
|
|
150
161
|
)
|
|
151
|
-
o[
|
|
162
|
+
o["unsend_events"] = []
|
|
152
163
|
try:
|
|
153
164
|
requests.post(self._api_endpoint, json=payload)
|
|
154
165
|
except Exception:
|
|
@@ -157,17 +168,17 @@ class Collector:
|
|
|
157
168
|
|
|
158
169
|
def _store_to_file(self, event):
|
|
159
170
|
with self.load_json() as o:
|
|
160
|
-
events = o.get(
|
|
171
|
+
events = o.get("unsend_events", None)
|
|
161
172
|
if events is None:
|
|
162
|
-
o[
|
|
173
|
+
o["unsend_events"] = []
|
|
163
174
|
|
|
164
|
-
o[
|
|
175
|
+
o["unsend_events"].append(event)
|
|
165
176
|
|
|
166
177
|
def _cleanup_unsend_events(self):
|
|
167
178
|
with self.load_json() as o:
|
|
168
|
-
events = o.get(
|
|
179
|
+
events = o.get("unsend_events", None)
|
|
169
180
|
if events is None:
|
|
170
|
-
o[
|
|
181
|
+
o["unsend_events"] = []
|
|
171
182
|
|
|
172
|
-
while len(o[
|
|
173
|
-
o[
|
|
183
|
+
while len(o["unsend_events"]) > self._delete_threshold:
|
|
184
|
+
o["unsend_events"].pop(0)
|
recce/event/track.py
CHANGED
|
@@ -18,8 +18,8 @@ from recce.git import current_branch, hosting_repo
|
|
|
18
18
|
|
|
19
19
|
console = Console()
|
|
20
20
|
|
|
21
|
-
_enable_traceback: bool = os.environ.get(
|
|
22
|
-
logger = logging.getLogger(
|
|
21
|
+
_enable_traceback: bool = os.environ.get("RECCE_PRINT_TRACEBACK") == "1"
|
|
22
|
+
logger = logging.getLogger("uvicorn")
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class TrackCommand(Command):
|
|
@@ -39,11 +39,23 @@ class TrackCommand(Command):
|
|
|
39
39
|
deprecated: bool = False,
|
|
40
40
|
beta: bool = False,
|
|
41
41
|
) -> None:
|
|
42
|
-
super(TrackCommand, self).__init__(
|
|
43
|
-
|
|
42
|
+
super(TrackCommand, self).__init__(
|
|
43
|
+
name,
|
|
44
|
+
context_settings,
|
|
45
|
+
callback,
|
|
46
|
+
params,
|
|
47
|
+
help,
|
|
48
|
+
epilog,
|
|
49
|
+
short_help,
|
|
50
|
+
options_metavar,
|
|
51
|
+
add_help_option,
|
|
52
|
+
no_args_is_help,
|
|
53
|
+
hidden,
|
|
54
|
+
deprecated,
|
|
55
|
+
)
|
|
44
56
|
|
|
45
57
|
def _show_error_message(self, msg, params):
|
|
46
|
-
if params.get(
|
|
58
|
+
if params.get("debug"):
|
|
47
59
|
console.print_exception(show_locals=True)
|
|
48
60
|
else:
|
|
49
61
|
print(traceback.format_exc())
|
|
@@ -51,39 +63,39 @@ class TrackCommand(Command):
|
|
|
51
63
|
# console.out(msg, highlight=False)
|
|
52
64
|
|
|
53
65
|
def _show_hint_message(self, hint):
|
|
54
|
-
console.print(f
|
|
66
|
+
console.print(f"[bold yellow]Hint[/bold yellow]:\n {escape(hint)}")
|
|
55
67
|
|
|
56
68
|
def invoke(self, ctx: Context) -> t.Any:
|
|
57
69
|
status = False
|
|
58
70
|
start_time = time.time()
|
|
59
|
-
reason =
|
|
60
|
-
event.set_exception_tag(
|
|
71
|
+
reason = "error"
|
|
72
|
+
event.set_exception_tag("command", ctx.command.name)
|
|
61
73
|
event.log_codespaces_events(ctx.command.name)
|
|
62
74
|
|
|
63
75
|
try:
|
|
64
76
|
ret = super(TrackCommand, self).invoke(ctx)
|
|
65
77
|
if ret is None or ret == 0:
|
|
66
78
|
status = True
|
|
67
|
-
reason =
|
|
79
|
+
reason = "ok"
|
|
68
80
|
else:
|
|
69
|
-
reason =
|
|
81
|
+
reason = "error"
|
|
70
82
|
sys.exit(ret)
|
|
71
83
|
return ret
|
|
72
84
|
except RecceException as e:
|
|
73
85
|
logger.debug(traceback.format_exc())
|
|
74
86
|
console.log("[Error] " + str(e))
|
|
75
|
-
reason =
|
|
87
|
+
reason = "error"
|
|
76
88
|
sys.exit(1)
|
|
77
89
|
except SystemExit as e:
|
|
78
|
-
reason =
|
|
90
|
+
reason = "error"
|
|
79
91
|
raise e
|
|
80
92
|
except KeyboardInterrupt as e:
|
|
81
|
-
reason =
|
|
93
|
+
reason = "aborted"
|
|
82
94
|
raise e
|
|
83
95
|
except Exception as e:
|
|
84
96
|
self._show_error_message(str(e), ctx.params)
|
|
85
97
|
event.capture_exception(e)
|
|
86
|
-
reason =
|
|
98
|
+
reason = "fatal"
|
|
87
99
|
event.flush_exceptions()
|
|
88
100
|
sys.exit(1)
|
|
89
101
|
finally:
|
|
@@ -93,32 +105,32 @@ class TrackCommand(Command):
|
|
|
93
105
|
branch = current_branch()
|
|
94
106
|
command = ctx.command.name
|
|
95
107
|
duration = end_time - start_time
|
|
96
|
-
target_path = ctx.params.get(
|
|
97
|
-
target_base_path = ctx.params.get(
|
|
108
|
+
target_path = ctx.params.get("target_path", None)
|
|
109
|
+
target_base_path = ctx.params.get("target_base_path", None)
|
|
98
110
|
props = dict(
|
|
99
111
|
command=command,
|
|
100
112
|
status=status,
|
|
101
113
|
reason=reason,
|
|
102
114
|
duration=duration,
|
|
103
|
-
cloud=ctx.params.get(
|
|
104
|
-
review=ctx.params.get(
|
|
105
|
-
debug=ctx.params.get(
|
|
115
|
+
cloud=ctx.params.get("cloud", False),
|
|
116
|
+
review=ctx.params.get("review", False),
|
|
117
|
+
debug=ctx.params.get("debug", False),
|
|
106
118
|
)
|
|
107
119
|
|
|
108
120
|
if runner is not None:
|
|
109
|
-
props[
|
|
121
|
+
props["runner_type"] = runner
|
|
110
122
|
|
|
111
123
|
if repo is not None:
|
|
112
|
-
props[
|
|
124
|
+
props["repository"] = sha256(repo.encode()).hexdigest()
|
|
113
125
|
|
|
114
126
|
if branch is not None:
|
|
115
|
-
props[
|
|
127
|
+
props["branch"] = sha256(branch.encode()).hexdigest()
|
|
116
128
|
|
|
117
129
|
if target_path is not None:
|
|
118
|
-
props[
|
|
130
|
+
props["target_path"] = sha256(target_path.encode()).hexdigest()
|
|
119
131
|
|
|
120
132
|
if target_base_path is not None:
|
|
121
|
-
props[
|
|
133
|
+
props["target_base_path"] = sha256(target_base_path.encode()).hexdigest()
|
|
122
134
|
|
|
123
135
|
try:
|
|
124
136
|
recce_context = load_context()
|
|
@@ -128,9 +140,19 @@ class TrackCommand(Command):
|
|
|
128
140
|
|
|
129
141
|
if recce_context is not None:
|
|
130
142
|
if recce_context.adapter_type == "dbt":
|
|
131
|
-
props[
|
|
143
|
+
props["adapter_type"] = "DBT"
|
|
144
|
+
# Add dbt warehouse type only for dbt adapter
|
|
145
|
+
try:
|
|
146
|
+
from recce.adapter.dbt_adapter import DbtAdapter
|
|
147
|
+
|
|
148
|
+
dbt_adapter: DbtAdapter = recce_context.adapter
|
|
149
|
+
warehouse_type = dbt_adapter.adapter.type()
|
|
150
|
+
props["warehouse_type"] = warehouse_type
|
|
151
|
+
except Exception:
|
|
152
|
+
# If we can't get the warehouse type, skip it
|
|
153
|
+
pass
|
|
132
154
|
elif recce_context.adapter_type == "sqlmesh":
|
|
133
|
-
props[
|
|
155
|
+
props["adapter_type"] = "SQLMesh"
|
|
134
156
|
|
|
135
|
-
event.log_event(props,
|
|
157
|
+
event.log_event(props, "command", params=ctx.params)
|
|
136
158
|
event.flush_events()
|
recce/exceptions.py
CHANGED
recce/git.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from git import
|
|
3
|
+
from git import InvalidGitRepositoryError, Repo
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def current_default_branch():
|
|
7
7
|
try:
|
|
8
8
|
repo = Repo(search_parent_directories=True)
|
|
9
|
-
return repo.remotes.origin.refs[
|
|
9
|
+
return repo.remotes.origin.refs["HEAD"].reference.remote_head
|
|
10
10
|
except Exception:
|
|
11
11
|
return None
|
|
12
12
|
|
|
@@ -53,19 +53,19 @@ def commit_hash_from_branch(branch: str, short_length: int = 7, short: bool = Fa
|
|
|
53
53
|
return None
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def hosting_repo(remote: str =
|
|
56
|
+
def hosting_repo(remote: str = "origin"):
|
|
57
57
|
try:
|
|
58
58
|
repo = Repo(search_parent_directories=True)
|
|
59
59
|
origin_url = repo.remote(name=remote).url
|
|
60
60
|
remote_repo = None
|
|
61
61
|
|
|
62
|
-
if origin_url.startswith(
|
|
62
|
+
if origin_url.startswith("git@"):
|
|
63
63
|
# Handle git@github.com:user/repo.git
|
|
64
|
-
remote_repo = origin_url.split(
|
|
64
|
+
remote_repo = origin_url.split(":")[1].replace(".git", "")
|
|
65
65
|
|
|
66
|
-
elif origin_url.startswith(
|
|
66
|
+
elif origin_url.startswith("https://") or origin_url.startswith("http://"):
|
|
67
67
|
# Handle https://github.com/user/repo.git or http://github.com/user/repo.git
|
|
68
|
-
remote_repo =
|
|
68
|
+
remote_repo = "/".join(origin_url.split("/")[-2:]).replace(".git", "")
|
|
69
69
|
|
|
70
70
|
return remote_repo
|
|
71
71
|
except ValueError:
|
recce/github.py
CHANGED
|
@@ -3,10 +3,10 @@ import os
|
|
|
3
3
|
import re
|
|
4
4
|
import zipfile
|
|
5
5
|
from datetime import datetime
|
|
6
|
-
from typing import List,
|
|
6
|
+
from typing import List, Optional, Tuple
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
|
-
from github import Artifact, Github,
|
|
9
|
+
from github import Artifact, Auth, Github, PullRequest, UnknownObjectException
|
|
10
10
|
|
|
11
11
|
from recce.git import current_branch, hosting_repo
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ def download_artifact(github_token: str, artifact: Artifact) -> List[str]:
|
|
|
16
16
|
Download the artifact from the Github.
|
|
17
17
|
"""
|
|
18
18
|
headers = {
|
|
19
|
-
|
|
19
|
+
"Authorization": f"Bearer {github_token}",
|
|
20
20
|
}
|
|
21
21
|
r = requests.get(artifact.archive_download_url, headers=headers)
|
|
22
22
|
r.raise_for_status()
|
|
@@ -27,17 +27,17 @@ def download_artifact(github_token: str, artifact: Artifact) -> List[str]:
|
|
|
27
27
|
|
|
28
28
|
def recce_ci_artifact(**kwargs):
|
|
29
29
|
"""
|
|
30
|
-
|
|
30
|
+
Download the artifact from the GitHub CI.
|
|
31
31
|
"""
|
|
32
|
-
from rich.console import Console
|
|
33
|
-
|
|
34
32
|
import git
|
|
33
|
+
|
|
35
34
|
# Authentication is defined via github.Auth
|
|
36
35
|
from github import Auth, Github
|
|
36
|
+
from rich.console import Console
|
|
37
37
|
|
|
38
38
|
console = Console()
|
|
39
|
-
github_token = kwargs.get(
|
|
40
|
-
github_repository = kwargs.get(
|
|
39
|
+
github_token = kwargs.get("github_token")
|
|
40
|
+
github_repository = kwargs.get("github_repository")
|
|
41
41
|
|
|
42
42
|
if github_token is None:
|
|
43
43
|
console.print("[[red]Error[/red]] Missing GitHub token. Please provide a GitHub token.")
|
|
@@ -70,11 +70,11 @@ def recce_ci_artifact(**kwargs):
|
|
|
70
70
|
|
|
71
71
|
head_branch = github_repo.get_branch(current_branch)
|
|
72
72
|
|
|
73
|
-
console.rule(
|
|
74
|
-
console.print(f
|
|
75
|
-
console.print(f
|
|
73
|
+
console.rule("GitHub Repository Information")
|
|
74
|
+
console.print(f"Repository: {github_repository}")
|
|
75
|
+
console.print(f"Current Branch: {current_branch}")
|
|
76
76
|
|
|
77
|
-
pull_requests = github_repo.get_pulls(head=f
|
|
77
|
+
pull_requests = github_repo.get_pulls(head=f"{github_owner}:{current_branch}")
|
|
78
78
|
if pull_requests.totalCount == 0:
|
|
79
79
|
# No pull request found for the current branch
|
|
80
80
|
console.print("[[red]Error[/red]] No pull request found for the current branch.")
|
|
@@ -82,32 +82,32 @@ def recce_ci_artifact(**kwargs):
|
|
|
82
82
|
|
|
83
83
|
pr = pull_requests[0]
|
|
84
84
|
|
|
85
|
-
console.rule(
|
|
86
|
-
console.print(f
|
|
87
|
-
console.print(f
|
|
88
|
-
console.print(f
|
|
89
|
-
console.print(f
|
|
85
|
+
console.rule("GitHub Pull Request Information")
|
|
86
|
+
console.print(f"{pr.title} - {pr.html_url}")
|
|
87
|
+
console.print(f"State: {pr.state.title()}")
|
|
88
|
+
console.print(f"Author: {pr.user.name}")
|
|
89
|
+
console.print(f"Created At: {pr.created_at}")
|
|
90
90
|
|
|
91
91
|
workflow_runs = github_repo.get_workflow_runs(
|
|
92
|
-
event=
|
|
93
|
-
status=
|
|
92
|
+
event="pull_request",
|
|
93
|
+
status="success",
|
|
94
94
|
branch=head_branch,
|
|
95
95
|
)
|
|
96
96
|
if workflow_runs.totalCount == 0:
|
|
97
97
|
console.print("[[yellow]Skip[/yellow]] No successful workflow runs found.")
|
|
98
98
|
return 0
|
|
99
99
|
last_workflow_run = workflow_runs[0]
|
|
100
|
-
console.rule(
|
|
101
|
-
console.print(f
|
|
100
|
+
console.rule("GitHub Workflow Run Information")
|
|
101
|
+
console.print(f"Last Workflow Run: {last_workflow_run.name} {last_workflow_run.html_url}")
|
|
102
102
|
artifacts = last_workflow_run.get_artifacts()
|
|
103
103
|
if artifacts.totalCount == 0:
|
|
104
104
|
console.print("[[yellow]Skip[/yellow]] No artifacts found.")
|
|
105
105
|
return 0
|
|
106
106
|
for artifact in artifacts:
|
|
107
|
-
console.print(f
|
|
107
|
+
console.print(f"Artifact: {artifact.name} {artifact.archive_download_url}")
|
|
108
108
|
artifact_files = download_artifact(github_token, artifact)
|
|
109
109
|
console.print(f'Extracted Files: {", ".join(artifact_files)}')
|
|
110
|
-
console.rule(
|
|
110
|
+
console.rule("Complete")
|
|
111
111
|
return 0
|
|
112
112
|
|
|
113
113
|
|
|
@@ -121,7 +121,7 @@ def get_pull_request(branch, owner, repo_name, github_token=None) -> Tuple[Optio
|
|
|
121
121
|
|
|
122
122
|
try:
|
|
123
123
|
repo = g.get_repo(f"{owner}/{repo_name}")
|
|
124
|
-
pulls = repo.get_pulls(state=
|
|
124
|
+
pulls = repo.get_pulls(state="open")
|
|
125
125
|
|
|
126
126
|
for pr in pulls:
|
|
127
127
|
if pr.head.ref == branch:
|
|
@@ -129,7 +129,10 @@ def get_pull_request(branch, owner, repo_name, github_token=None) -> Tuple[Optio
|
|
|
129
129
|
|
|
130
130
|
except UnknownObjectException:
|
|
131
131
|
if github_token is not None:
|
|
132
|
-
return
|
|
132
|
+
return (
|
|
133
|
+
None,
|
|
134
|
+
f"Repository {owner}/{repo_name} not found. If it is private repo, please add the 'repo' scope to the token.",
|
|
135
|
+
)
|
|
133
136
|
|
|
134
137
|
return None, None
|
|
135
138
|
|
|
@@ -139,53 +142,54 @@ def recce_pr_information(github_token=None) -> Tuple[Optional[type(PullRequest)]
|
|
|
139
142
|
repo = hosting_repo()
|
|
140
143
|
|
|
141
144
|
if not repo:
|
|
142
|
-
return None,
|
|
143
|
-
if
|
|
144
|
-
return None,
|
|
145
|
+
return None, "This is not a git repository."
|
|
146
|
+
if "/" not in repo:
|
|
147
|
+
return None, "This is not a GitHub repository."
|
|
145
148
|
|
|
146
|
-
owner, repo_name = repo.split(
|
|
149
|
+
owner, repo_name = repo.split("/")
|
|
147
150
|
|
|
148
151
|
github_token = github_token if github_token else os.getenv("GITHUB_TOKEN")
|
|
149
152
|
return get_pull_request(branch, owner, repo_name, github_token)
|
|
150
153
|
|
|
151
154
|
|
|
152
155
|
def is_github_codespace():
|
|
153
|
-
return os.getenv(
|
|
156
|
+
return os.getenv("CODESPACES") == "true"
|
|
154
157
|
|
|
155
158
|
|
|
156
159
|
def get_github_codespace_name():
|
|
157
|
-
return os.getenv(
|
|
160
|
+
return os.getenv("CODESPACE_NAME")
|
|
158
161
|
|
|
159
162
|
|
|
160
163
|
def get_github_codespace_info():
|
|
161
164
|
if is_github_codespace() is False:
|
|
162
165
|
return None
|
|
163
166
|
|
|
164
|
-
codespace_name = os.environ.get(
|
|
165
|
-
github_token = os.environ.get(
|
|
167
|
+
codespace_name = os.environ.get("CODESPACE_NAME")
|
|
168
|
+
github_token = os.environ.get("GITHUB_TOKEN")
|
|
166
169
|
|
|
167
170
|
response = requests.get(
|
|
168
|
-
f
|
|
171
|
+
f"https://api.github.com/user/codespaces/{codespace_name}",
|
|
169
172
|
headers={
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
173
|
+
"Accept": "application/vnd.github+json",
|
|
174
|
+
"Authorization": f"token {github_token}",
|
|
175
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
176
|
+
},
|
|
177
|
+
)
|
|
174
178
|
|
|
175
179
|
if response.status_code != 200:
|
|
176
180
|
return None
|
|
177
181
|
codespace_info = response.json()
|
|
178
182
|
|
|
179
183
|
return dict(
|
|
180
|
-
name=codespace_info.get(
|
|
181
|
-
machine=codespace_info.get(
|
|
182
|
-
prebuild=codespace_info.get(
|
|
183
|
-
created_at=codespace_info.get(
|
|
184
|
-
updated_at=codespace_info.get(
|
|
185
|
-
last_used_at=codespace_info.get(
|
|
186
|
-
state=codespace_info.get(
|
|
187
|
-
location=codespace_info.get(
|
|
188
|
-
idle_timeout_minutes=codespace_info.get(
|
|
184
|
+
name=codespace_info.get("name"),
|
|
185
|
+
machine=codespace_info.get("machine"),
|
|
186
|
+
prebuild=codespace_info.get("prebuild"),
|
|
187
|
+
created_at=codespace_info.get("created_at"),
|
|
188
|
+
updated_at=codespace_info.get("updated_at"),
|
|
189
|
+
last_used_at=codespace_info.get("last_used_at"),
|
|
190
|
+
state=codespace_info.get("state"),
|
|
191
|
+
location=codespace_info.get("location"),
|
|
192
|
+
idle_timeout_minutes=codespace_info.get("idle_timeout_minutes"),
|
|
189
193
|
)
|
|
190
194
|
|
|
191
195
|
|
|
@@ -194,25 +198,25 @@ def get_github_codespace_available_at(codespace):
|
|
|
194
198
|
return None
|
|
195
199
|
|
|
196
200
|
def search_in_file(file_path, search_string):
|
|
197
|
-
with open(file_path,
|
|
201
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
198
202
|
for _, line in enumerate(f, 1):
|
|
199
203
|
if search_string in line:
|
|
200
204
|
return line
|
|
201
205
|
return None
|
|
202
206
|
|
|
203
207
|
def extract_datatime(log_line):
|
|
204
|
-
pattern = r
|
|
208
|
+
pattern = r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}).*\]"
|
|
205
209
|
match = re.search(pattern, log_line)
|
|
206
210
|
if match:
|
|
207
211
|
datetime_str = match.group(1)
|
|
208
|
-
return datetime.strptime(datetime_str,
|
|
212
|
+
return datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S.%f")
|
|
209
213
|
return None
|
|
210
214
|
|
|
211
|
-
github_codepsce_log_dir =
|
|
215
|
+
github_codepsce_log_dir = "/tmp/codespaces_logs"
|
|
212
216
|
try:
|
|
213
217
|
log_file = os.listdir(github_codepsce_log_dir)[-1] # Get the latest log file
|
|
214
|
-
start_monitor_line = search_in_file(f
|
|
218
|
+
start_monitor_line = search_in_file(f"{github_codepsce_log_dir}/{log_file}", "Starting monitor")
|
|
215
219
|
return extract_datatime(start_monitor_line)
|
|
216
220
|
except Exception:
|
|
217
221
|
# If there is any error, use the updated_at time from the codespace info
|
|
218
|
-
return datetime.fromisoformat(codespace.get(
|
|
222
|
+
return datetime.fromisoformat(codespace.get("updated_at"))
|