recce-nightly 1.9.0.20250622__py3-none-any.whl → 1.9.0.20250624__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/adapter/dbt_adapter/__init__.py +108 -45
- recce/cli.py +42 -11
- recce/connect_to_cloud.py +138 -0
- recce/data/404.html +2 -2
- recce/data/_next/static/chunks/{92-7ab55ae02606193c.js → 92-607cd1af83c41f43.js} +1 -1
- recce/data/_next/static/chunks/app/page-3f79c2e6d6f03a09.js +1 -0
- recce/data/auth_callback.html +68 -0
- recce/data/index.html +2 -2
- recce/data/index.txt +3 -3
- recce/models/types.py +7 -0
- recce/server.py +37 -33
- recce/state.py +3 -2
- recce/util/api_token.py +1 -9
- recce/util/lineage.py +16 -12
- recce/util/onboarding_state.py +45 -0
- recce/util/recce_cloud.py +7 -7
- {recce_nightly-1.9.0.20250622.dist-info → recce_nightly-1.9.0.20250624.dist-info}/METADATA +1 -1
- {recce_nightly-1.9.0.20250622.dist-info → recce_nightly-1.9.0.20250624.dist-info}/RECORD +27 -23
- tests/adapter/dbt_adapter/test_dbt_cll.py +9 -9
- tests/test_connect_to_cloud.py +82 -0
- recce/data/_next/static/chunks/app/page-707b8e8d98d7f780.js +0 -1
- /recce/data/_next/static/{M3_t2h-wA4_IXOtKHdJqV → Iga3yMdHBVJ_Vhluh2SdE}/_buildManifest.js +0 -0
- /recce/data/_next/static/{M3_t2h-wA4_IXOtKHdJqV → Iga3yMdHBVJ_Vhluh2SdE}/_ssgManifest.js +0 -0
- {recce_nightly-1.9.0.20250622.dist-info → recce_nightly-1.9.0.20250624.dist-info}/WHEEL +0 -0
- {recce_nightly-1.9.0.20250622.dist-info → recce_nightly-1.9.0.20250624.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.9.0.20250622.dist-info → recce_nightly-1.9.0.20250624.dist-info}/licenses/LICENSE +0 -0
- {recce_nightly-1.9.0.20250622.dist-info → recce_nightly-1.9.0.20250624.dist-info}/top_level.txt +0 -0
recce/server.py
CHANGED
|
@@ -8,7 +8,7 @@ from contextlib import asynccontextmanager
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from datetime import datetime, timedelta
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Annotated, Any,
|
|
11
|
+
from typing import Annotated, Any, Literal, Optional, Set
|
|
12
12
|
|
|
13
13
|
from fastapi import (
|
|
14
14
|
BackgroundTasks,
|
|
@@ -33,8 +33,15 @@ from . import __latest_version__, __version__, event
|
|
|
33
33
|
from .apis.check_api import check_router
|
|
34
34
|
from .apis.run_api import run_router
|
|
35
35
|
from .config import RecceConfig
|
|
36
|
+
from .connect_to_cloud import (
|
|
37
|
+
connect_to_cloud_background_task,
|
|
38
|
+
generate_key_pair,
|
|
39
|
+
get_connection_url,
|
|
40
|
+
is_callback_server_running,
|
|
41
|
+
prepare_connection_url,
|
|
42
|
+
)
|
|
36
43
|
from .core import RecceContext, default_context, load_context
|
|
37
|
-
from .event import log_api_event, log_single_env_event
|
|
44
|
+
from .event import get_recce_api_token, log_api_event, log_single_env_event
|
|
38
45
|
from .exceptions import RecceException
|
|
39
46
|
from .models.types import CllData
|
|
40
47
|
from .run import load_preset_checks
|
|
@@ -247,8 +254,7 @@ async def recce_instance_info():
|
|
|
247
254
|
read_only = flag.get("read_only", False)
|
|
248
255
|
single_env = flag.get("single_env_onboarding", False)
|
|
249
256
|
|
|
250
|
-
|
|
251
|
-
api_token = auth_options.get("api_token")
|
|
257
|
+
api_token = get_recce_api_token()
|
|
252
258
|
|
|
253
259
|
return {
|
|
254
260
|
"read_only": read_only,
|
|
@@ -271,13 +277,6 @@ async def config_flag():
|
|
|
271
277
|
return flag
|
|
272
278
|
|
|
273
279
|
|
|
274
|
-
@app.post("/api/onboarding/completed", status_code=204)
|
|
275
|
-
async def mark_onboarding_completed():
|
|
276
|
-
context = default_context()
|
|
277
|
-
context.mark_onboarding_completed()
|
|
278
|
-
app.state.flag["show_onboarding_guide"] = False
|
|
279
|
-
|
|
280
|
-
|
|
281
280
|
@app.post("/api/relaunch-hint/completed", status_code=204)
|
|
282
281
|
async def mark_relaunch_hint_completed():
|
|
283
282
|
app.state.flag["show_relaunch_hint"] = False
|
|
@@ -335,7 +334,12 @@ async def get_info():
|
|
|
335
334
|
|
|
336
335
|
|
|
337
336
|
class CllIn(BaseModel):
|
|
338
|
-
|
|
337
|
+
node_id: Optional[str] = None
|
|
338
|
+
column: Optional[str] = None
|
|
339
|
+
change_analysis: Optional[bool] = False
|
|
340
|
+
cll: Optional[bool] = False
|
|
341
|
+
upstream: Optional[bool] = False
|
|
342
|
+
downstream: Optional[bool] = False
|
|
339
343
|
|
|
340
344
|
|
|
341
345
|
class CllOutput(BaseModel):
|
|
@@ -347,27 +351,13 @@ async def column_level_lineage_by_node(cll_input: CllIn):
|
|
|
347
351
|
from recce.adapter.dbt_adapter import DbtAdapter
|
|
348
352
|
|
|
349
353
|
dbt_adapter: DbtAdapter = default_context().adapter
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
class ImpactRadiusIn(BaseModel):
|
|
359
|
-
node_id: str
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
@app.post("/api/impact-radius", response_model=CllOutput)
|
|
363
|
-
async def impact_radius_by_node(impact_input: ImpactRadiusIn):
|
|
364
|
-
from recce.adapter.dbt_adapter import DbtAdapter
|
|
365
|
-
|
|
366
|
-
context = default_context()
|
|
367
|
-
dbt_adapter: DbtAdapter = context.adapter
|
|
368
|
-
node_id = impact_input.node_id
|
|
369
|
-
|
|
370
|
-
cll = dbt_adapter.get_impact_radius(node_id)
|
|
354
|
+
cll = dbt_adapter.get_cll(
|
|
355
|
+
node_id=cll_input.node_id,
|
|
356
|
+
column=cll_input.column,
|
|
357
|
+
change_analysis=cll_input.change_analysis,
|
|
358
|
+
upstream=cll_input.upstream,
|
|
359
|
+
downstream=cll_input.downstream,
|
|
360
|
+
)
|
|
371
361
|
|
|
372
362
|
return CllOutput(current=cll)
|
|
373
363
|
|
|
@@ -670,6 +660,20 @@ async def broadcast(data: str):
|
|
|
670
660
|
await client.send_text(data)
|
|
671
661
|
|
|
672
662
|
|
|
663
|
+
@app.post("/api/connect")
|
|
664
|
+
async def generate_connect_to_cloud_url(background_tasks: BackgroundTasks):
|
|
665
|
+
if is_callback_server_running():
|
|
666
|
+
return {"connection_url": get_connection_url()}
|
|
667
|
+
|
|
668
|
+
private_key, public_key = generate_key_pair()
|
|
669
|
+
connection_url, callback_port = prepare_connection_url(public_key)
|
|
670
|
+
|
|
671
|
+
background_tasks.add_task(connect_to_cloud_background_task, private_key, callback_port, connection_url)
|
|
672
|
+
return {
|
|
673
|
+
"connection_url": connection_url,
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
|
|
673
677
|
api_prefix = "/api"
|
|
674
678
|
app.include_router(check_router, prefix=api_prefix)
|
|
675
679
|
app.include_router(run_router, prefix=api_prefix)
|
recce/state.py
CHANGED
|
@@ -16,6 +16,7 @@ import botocore.exceptions
|
|
|
16
16
|
from pydantic import BaseModel, Field
|
|
17
17
|
|
|
18
18
|
from recce import get_version
|
|
19
|
+
from recce.event import get_recce_api_token
|
|
19
20
|
from recce.git import current_branch
|
|
20
21
|
from recce.models import CheckDAO
|
|
21
22
|
from recce.models.types import Check, Run
|
|
@@ -766,7 +767,7 @@ class RecceShareStateManager:
|
|
|
766
767
|
self.hint_message = None
|
|
767
768
|
|
|
768
769
|
def verify(self) -> bool:
|
|
769
|
-
if
|
|
770
|
+
if get_recce_api_token() is None:
|
|
770
771
|
self.error_message = RECCE_API_TOKEN_MISSING.error_message
|
|
771
772
|
self.hint_message = RECCE_API_TOKEN_MISSING.hint_message
|
|
772
773
|
return False
|
|
@@ -781,5 +782,5 @@ class RecceShareStateManager:
|
|
|
781
782
|
|
|
782
783
|
with tempfile.NamedTemporaryFile() as tmp:
|
|
783
784
|
state.to_file(tmp.name, file_type=SupportedFileTypes.FILE)
|
|
784
|
-
response = RecceCloud(token=
|
|
785
|
+
response = RecceCloud(token=get_recce_api_token()).share_state(file_name, open(tmp.name, "rb"))
|
|
785
786
|
return response
|
recce/util/api_token.py
CHANGED
|
@@ -7,8 +7,6 @@ from recce.exceptions import RecceConfigException
|
|
|
7
7
|
from recce.util.recce_cloud import (
|
|
8
8
|
RECCE_CLOUD_BASE_URL,
|
|
9
9
|
RecceCloud,
|
|
10
|
-
get_recce_cloud_onboarding_state,
|
|
11
|
-
set_recce_cloud_onboarding_state,
|
|
12
10
|
)
|
|
13
11
|
|
|
14
12
|
console = Console()
|
|
@@ -54,7 +52,7 @@ def prepare_api_token(
|
|
|
54
52
|
event.log_connected_to_cloud()
|
|
55
53
|
else:
|
|
56
54
|
# No api_token provided
|
|
57
|
-
if interaction
|
|
55
|
+
if interaction:
|
|
58
56
|
console.print(
|
|
59
57
|
"An API token is required for this feature. This can be obtained in your user account settings.\n"
|
|
60
58
|
f"{RECCE_CLOUD_BASE_URL}/settings#tokens\n"
|
|
@@ -70,10 +68,4 @@ def prepare_api_token(
|
|
|
70
68
|
"You no longer need to append --api-token to the recce command"
|
|
71
69
|
)
|
|
72
70
|
|
|
73
|
-
if api_token:
|
|
74
|
-
cloud_onboarding_state = get_recce_cloud_onboarding_state(api_token)
|
|
75
|
-
if cloud_onboarding_state == "new":
|
|
76
|
-
# Mark the onboarding state as "launched" if the user is new
|
|
77
|
-
set_recce_cloud_onboarding_state(api_token, "launched")
|
|
78
|
-
|
|
79
71
|
return api_token
|
recce/util/lineage.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from typing import Dict, Set, Tuple
|
|
1
|
+
from typing import Dict, Iterable, Set, Tuple
|
|
2
2
|
|
|
3
3
|
from recce.models.types import CllColumn, CllNode
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def find_upstream(
|
|
6
|
+
def find_upstream(node_ids: Iterable, parent_map):
|
|
7
7
|
visited = set()
|
|
8
8
|
upstream = set()
|
|
9
9
|
|
|
@@ -17,11 +17,13 @@ def find_upstream(node, parent_map):
|
|
|
17
17
|
upstream.add(parent)
|
|
18
18
|
dfs(parent)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
for node_id in node_ids:
|
|
21
|
+
dfs(node_id)
|
|
22
|
+
|
|
21
23
|
return upstream
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
def find_downstream(
|
|
26
|
+
def find_downstream(node_ids: Iterable, child_map):
|
|
25
27
|
visited = set()
|
|
26
28
|
downstream = set()
|
|
27
29
|
|
|
@@ -35,13 +37,15 @@ def find_downstream(node, child_map):
|
|
|
35
37
|
downstream.add(child)
|
|
36
38
|
dfs(child)
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
for node_id in node_ids:
|
|
41
|
+
dfs(node_id)
|
|
42
|
+
|
|
39
43
|
return downstream
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
def find_column_dependencies(node_column_id: str, parent_map: Dict, child_map: Dict) -> Tuple[Set, Set]:
|
|
43
|
-
upstream_cols = find_upstream(node_column_id, parent_map)
|
|
44
|
-
downstream_cols = find_downstream(node_column_id, child_map)
|
|
47
|
+
upstream_cols = find_upstream([node_column_id], parent_map)
|
|
48
|
+
downstream_cols = find_downstream([node_column_id], child_map)
|
|
45
49
|
return upstream_cols, downstream_cols
|
|
46
50
|
|
|
47
51
|
|
|
@@ -63,16 +67,16 @@ def filter_lineage_vertices(
|
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
def filter_dependency_maps(
|
|
66
|
-
parent_map: Dict, child_map: Dict,
|
|
70
|
+
parent_map: Dict, child_map: Dict, relevant_ids: Set
|
|
67
71
|
) -> Tuple[Dict[str, Set], Dict[str, Set]]:
|
|
68
72
|
p_map = {}
|
|
69
73
|
c_map = {}
|
|
70
74
|
for node_id, parents in parent_map.items():
|
|
71
|
-
if node_id in
|
|
72
|
-
p_map[node_id] = {p for p in parents if p in
|
|
75
|
+
if node_id in relevant_ids:
|
|
76
|
+
p_map[node_id] = {p for p in parents if p in relevant_ids}
|
|
73
77
|
|
|
74
78
|
for node_id, children in child_map.items():
|
|
75
|
-
if node_id in
|
|
76
|
-
c_map[node_id] = {c for c in children if c in
|
|
79
|
+
if node_id in relevant_ids:
|
|
80
|
+
c_map[node_id] = {c for c in children if c in relevant_ids}
|
|
77
81
|
|
|
78
82
|
return p_map, c_map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from recce.util.recce_cloud import (
|
|
5
|
+
get_recce_cloud_onboarding_state,
|
|
6
|
+
set_recce_cloud_onboarding_state,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OnboardingState(Enum):
|
|
11
|
+
SIGNUP_SUCCESSFUL = "signup_successful"
|
|
12
|
+
LAUNCHED_WITH_TOKEN = "launched_with_token"
|
|
13
|
+
CONFIGURE_TWO_ENV = "configure_two_env"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def update_onboarding_state(api_token: Union[str, bool, None], is_single_env: bool) -> OnboardingState:
|
|
17
|
+
if api_token:
|
|
18
|
+
# existing onboarding_state values -> new, launched, launched_with_two_envs
|
|
19
|
+
# new -> launched
|
|
20
|
+
# new -> launched_with_two_envs
|
|
21
|
+
# launched -> launched_with_two_envs
|
|
22
|
+
cloud_onboarding_state = get_recce_cloud_onboarding_state(api_token)
|
|
23
|
+
|
|
24
|
+
if cloud_onboarding_state == OnboardingState.SIGNUP_SUCCESSFUL.value:
|
|
25
|
+
# User has an API Token and is a "new" user
|
|
26
|
+
if is_single_env:
|
|
27
|
+
# Mark the onboarding state as "launched" if the user is new
|
|
28
|
+
set_recce_cloud_onboarding_state(api_token, OnboardingState.LAUNCHED_WITH_TOKEN.value)
|
|
29
|
+
return OnboardingState.LAUNCHED_WITH_TOKEN
|
|
30
|
+
else:
|
|
31
|
+
set_recce_cloud_onboarding_state(api_token, OnboardingState.CONFIGURE_TWO_ENV.value)
|
|
32
|
+
return OnboardingState.CONFIGURE_TWO_ENV
|
|
33
|
+
elif cloud_onboarding_state == OnboardingState.LAUNCHED_WITH_TOKEN.value:
|
|
34
|
+
# User has an API Token and has Two Environments
|
|
35
|
+
if is_single_env:
|
|
36
|
+
# Just return the current state
|
|
37
|
+
return OnboardingState.LAUNCHED_WITH_TOKEN
|
|
38
|
+
else:
|
|
39
|
+
set_recce_cloud_onboarding_state(api_token, OnboardingState.CONFIGURE_TWO_ENV.value)
|
|
40
|
+
return OnboardingState.CONFIGURE_TWO_ENV
|
|
41
|
+
elif cloud_onboarding_state == OnboardingState.CONFIGURE_TWO_ENV.value:
|
|
42
|
+
# Just return the current state
|
|
43
|
+
return OnboardingState.CONFIGURE_TWO_ENV
|
|
44
|
+
|
|
45
|
+
return OnboardingState.SIGNUP_SUCCESSFUL
|
recce/util/recce_cloud.py
CHANGED
|
@@ -180,13 +180,13 @@ class RecceCloud:
|
|
|
180
180
|
|
|
181
181
|
def set_onboarding_state(self, state: str):
|
|
182
182
|
api_url = f"{self.base_url}/users/onboarding-state"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
183
|
+
try:
|
|
184
|
+
response = self._request("PUT", api_url, json={"state": state})
|
|
185
|
+
response.raise_for_status()
|
|
186
|
+
except requests.exceptions.HTTPError as e:
|
|
187
|
+
# Don't Raise an exception if setting onboarding_state fails
|
|
188
|
+
logger.warning(f"Failed to set Onboarding State in Recce Cloud. Reason: {str(e)}")
|
|
189
|
+
return
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|
def get_recce_cloud_onboarding_state(token: str) -> str:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
recce/VERSION,sha256=
|
|
1
|
+
recce/VERSION,sha256=PM9mUjBu33bctG1WGN9EvPjwMgQRl2NheT822LUdTlc,15
|
|
2
2
|
recce/__init__.py,sha256=yNb0QT-yoStex0VZALNJvUwtPLommoVCStcow31guqo,2392
|
|
3
3
|
recce/artifact.py,sha256=tKQAHSrLRjiR3ppOI4sym8SxYiiLTuD3DPMYh4DWQdA,6506
|
|
4
|
-
recce/cli.py,sha256=
|
|
4
|
+
recce/cli.py,sha256=4uFTt2vWlLikySC2TsV3j_obUyfeg0L2vEW1cqXQprY,40529
|
|
5
5
|
recce/config.py,sha256=fs22mpFj8CFIxftGbhFAV5xIsPLX2xNTwWSer3UYn5k,4658
|
|
6
|
+
recce/connect_to_cloud.py,sha256=_SkX2pdyXa9FNyaHvItyYVPF2nZxy2JnCyd_o_psh2Y,4750
|
|
6
7
|
recce/core.py,sha256=3Nv2QWBGz3yj1QI0wyPNRaDj1rH80DsX2qRQn9ydpg0,10747
|
|
7
8
|
recce/diff.py,sha256=L2_bzQ3__PO-0aeir8PHF8FvSOUmQ8WcDXgML1-mHdY,748
|
|
8
9
|
recce/exceptions.py,sha256=SclQ678GrHGjw7p_ZFJ3vZaL_yMU5xABeIAm2u_W2bk,592
|
|
@@ -10,25 +11,26 @@ recce/git.py,sha256=8Eg-6NzL-KjA3rT-ibbAyaCwGlzV0JqH3yGikrJNMDA,2344
|
|
|
10
11
|
recce/github.py,sha256=PEpM6ZRiinsPbXSWj4aJCKbZrN1jUXzpzAfJq_CGah4,7420
|
|
11
12
|
recce/pull_request.py,sha256=aW0B1NE2LUKTam1S4TQ7smXB9KLE1DV8GnyBqNXA6j8,3832
|
|
12
13
|
recce/run.py,sha256=LAjbWUF8loZ9cL25d_maQELwROHHmfaFn7iqFRy5O1o,13567
|
|
13
|
-
recce/server.py,sha256=
|
|
14
|
-
recce/state.py,sha256=
|
|
14
|
+
recce/server.py,sha256=RFbZQP9RAsbtGEgtP-ltLc9ZvBZZM6T9qEn9nIwh3HY,20944
|
|
15
|
+
recce/state.py,sha256=fOzy09rn96XqOQuA0RcMW-k2KpjNVH9FQhihEXLvlA0,30634
|
|
15
16
|
recce/summary.py,sha256=Mbxvxr9KazR5o9icqhhjiGHsoAiWxQU4PdN7HytBJ1c,19154
|
|
16
17
|
recce/adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
18
|
recce/adapter/base.py,sha256=T_JNeLHgiHSaegw-DbrvHOaYjMyZcjj2Qtg5cWh_fco,3548
|
|
18
19
|
recce/adapter/sqlmesh_adapter.py,sha256=IU3N-F6ToDoO7_bV5vsG8pmTuDcbFtewTIuCxedTaRM,5046
|
|
19
|
-
recce/adapter/dbt_adapter/__init__.py,sha256=
|
|
20
|
+
recce/adapter/dbt_adapter/__init__.py,sha256=TqxYCxIe675XswFmx57hW55GV-YgC9pGMZW4mikVvV4,62852
|
|
20
21
|
recce/adapter/dbt_adapter/dbt_version.py,sha256=M7aedZIWslXnJsryK8Ki4OL_t2oAKxy4uE2pRwfWIkk,1228
|
|
21
22
|
recce/apis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
23
|
recce/apis/check_api.py,sha256=KMCXSMl1qqzx2jQgRqCrD4j_cY3EHBbM3H2-t-6saAU,6227
|
|
23
24
|
recce/apis/check_func.py,sha256=gktbCcyk3WGvWRJJ-wDnwv7NrIny2nTHWLl1-kdiVRo,4183
|
|
24
25
|
recce/apis/run_api.py,sha256=eOaxOxXDkH59uqGCd4blld7edavUx7JU_DCd2WAYrL8,3416
|
|
25
26
|
recce/apis/run_func.py,sha256=6wC8TDU-h7TLr2VZH7HNsWaUVlQ9HBN5N_dwqfi4lMY,7440
|
|
26
|
-
recce/data/404.html,sha256=
|
|
27
|
+
recce/data/404.html,sha256=ZSuzOByvWRvnqWk8gxsrw8_5FmBt0mvHqXbSowXEK70,26574
|
|
28
|
+
recce/data/auth_callback.html,sha256=2QnhHRc2eLPiaxCYl-HndNsGDGmM6jZAMbqoMlehal4,94783
|
|
27
29
|
recce/data/favicon.ico,sha256=B2mBumUOnzvUrXrqNkrc5QfdDXjzEXRcWkWur0fJ6sM,2565
|
|
28
|
-
recce/data/index.html,sha256=
|
|
29
|
-
recce/data/index.txt,sha256=
|
|
30
|
-
recce/data/_next/static/
|
|
31
|
-
recce/data/_next/static/
|
|
30
|
+
recce/data/index.html,sha256=MybT9NbtYA-o_HIVCKS6agiqjoATOg2BFI19JZCBcvs,42155
|
|
31
|
+
recce/data/index.txt,sha256=f5XOwdv9lEn96-mYbI4hur7bUvreWiTlQX1vdf43l9o,4462
|
|
32
|
+
recce/data/_next/static/Iga3yMdHBVJ_Vhluh2SdE/_buildManifest.js,sha256=sjy6FmOqWlkXWmod2oXwqKv7L582_MTycs9THCE6G-c,224
|
|
33
|
+
recce/data/_next/static/Iga3yMdHBVJ_Vhluh2SdE/_ssgManifest.js,sha256=Z49s4suAsf5y_GfnQSvm4qtq2ggxEbZPfEDTXjy6XgA,80
|
|
32
34
|
recce/data/_next/static/chunks/181-acc61ddada3bc0ca.js,sha256=2zRVkcaI906lYW_aM2mNqnt2HbLxmUaYFjft7eNcuTQ,310191
|
|
33
35
|
recce/data/_next/static/chunks/1bff33f1-1ef85cf5e658a751.js,sha256=wTH-_PbE1XdP-wmGaQStF5y2yH6XcZXeYX_hDaGMfh4,153777
|
|
34
36
|
recce/data/_next/static/chunks/217-879a84d70f7a907c.js,sha256=kNJkLcwaIlWnQY7akbrFimbcHHdK6lMVTbTYCNKXIjc,124102
|
|
@@ -45,7 +47,7 @@ recce/data/_next/static/chunks/7a8a3e83-edf6dc64b5d5f0a5.js,sha256=NfgBK9op5SzWm
|
|
|
45
47
|
recce/data/_next/static/chunks/7f27ae6c-d5f0438edd5c2a5b.js,sha256=ewMLioJbTUUDeqV0J9NDKE0Bj-XCFg8QqMLOmLPgpcc,777
|
|
46
48
|
recce/data/_next/static/chunks/86730205-cfb14e3f051bab35.js,sha256=qKkzgWsNqSrn-PUGBloZopIlacGCy8hH0BZA9tpqQoQ,172837
|
|
47
49
|
recce/data/_next/static/chunks/8d700b6a.8bb140898499c512.js,sha256=NPGWvqv8Y_rbNaKxYnoq16mi6FDoQHxOOjpH4tJnUkE,119972
|
|
48
|
-
recce/data/_next/static/chunks/92-
|
|
50
|
+
recce/data/_next/static/chunks/92-607cd1af83c41f43.js,sha256=0ThWZlPjuwxRRFkwB0qN9Q6QdMxJzdGhuIyLAOHO4lw,80466
|
|
49
51
|
recce/data/_next/static/chunks/9746af58-a42b7d169cacadf0.js,sha256=lV-V6BYpd4HEt-pFtT03G6qQD2Wa-2L_jp6aH9VgoeM,4177
|
|
50
52
|
recce/data/_next/static/chunks/a30376cd-de84559016d7e133.js,sha256=qzgyjrX5PdPKGeSwb-Gh0RfM_NEF2zjMvMK14ixq228,864
|
|
51
53
|
recce/data/_next/static/chunks/b63b1b3f-4282bdcf459e075c.js,sha256=s2EZ59zDBJDmy2kh0cHkQ_pAWxiQr_yCPlnttCj0x_k,572
|
|
@@ -60,7 +62,7 @@ recce/data/_next/static/chunks/main-b5b3ae20a1405261.js,sha256=28hJH2VsiTdxZGZrQ
|
|
|
60
62
|
recce/data/_next/static/chunks/polyfills-42372ed130431b0a.js,sha256=CXPB1kyIrcjjyVBBDLWLKI9yEY1ZZbeASUON648vloM,112594
|
|
61
63
|
recce/data/_next/static/chunks/webpack-7b49d5ba7e3a434d.js,sha256=lcK59Juk4EZqi3jIsY35lQqAlB5TLTPy-neri0BIZL0,3929
|
|
62
64
|
recce/data/_next/static/chunks/app/layout-177a410a97e0d018.js,sha256=Hmy6Lmrt4GPYYa0f1qhxTiMj4K47PCmkAkrm1Rbnd7w,1731
|
|
63
|
-
recce/data/_next/static/chunks/app/page-
|
|
65
|
+
recce/data/_next/static/chunks/app/page-3f79c2e6d6f03a09.js,sha256=N3h3lkuP_o97aLrOd0lZp--oJqjuw_SOv733KWqOCM0,156413
|
|
64
66
|
recce/data/_next/static/chunks/app/_not-found/page-01ed58b7f971d311.js,sha256=_l3JSAaIJxInWqyqdDsobkhJ3JP6DZuwXkw-MsRakQA,1751
|
|
65
67
|
recce/data/_next/static/chunks/pages/_app-437c455677d62394.js,sha256=FaxURD1wuAmnMU1zJlsQKT9rRdfuspR7yx7BckcoCLY,284
|
|
66
68
|
recce/data/_next/static/chunks/pages/_error-e7650df18ca04bde.js,sha256=0Vzx_hCqw4RcptsFzHfUigpI13sToBnFw38owTP7ruo,250
|
|
@@ -91,7 +93,7 @@ recce/event/track.py,sha256=xDyDWblhR6mp0tezEYhkA0aOyZLqXBpJyHZwhI2xhU8,4803
|
|
|
91
93
|
recce/models/__init__.py,sha256=F7cgALtdWnwv37R0eEgKZ_yBsMwxWnUfo3jAZ3u6qyU,209
|
|
92
94
|
recce/models/check.py,sha256=jjR1SGyczjrqyK-0Zas6ikLIGSgVp54lvfcQA19AniI,1588
|
|
93
95
|
recce/models/run.py,sha256=QK2gvOWvko9YYhd2NLs3BPt5l4MSCZGwpzTAiqx9zJw,1161
|
|
94
|
-
recce/models/types.py,sha256=
|
|
96
|
+
recce/models/types.py,sha256=0YbhuL_MiDinhtqq9UQJQYu5_zTNWOjm7G5t-WKbDV0,4390
|
|
95
97
|
recce/tasks/__init__.py,sha256=b553AtDHjYROgmMePv_Hv_X3fjh4iEn11gzzpUJz6_o,610
|
|
96
98
|
recce/tasks/core.py,sha256=JFYa1CfgOiRPQ7KVTwMuxJjhMB-pvCwB-xezVt-h3RU,4080
|
|
97
99
|
recce/tasks/dataframe.py,sha256=03UBWwt0DFTXlaEOtnV5i_mxdRKD7UbRayewEL2Ub48,3650
|
|
@@ -104,21 +106,23 @@ recce/tasks/schema.py,sha256=HHrSvhd_ZJdrNj2QKi9W8vmig0NuYci5cgsKFvp2bu4,2274
|
|
|
104
106
|
recce/tasks/top_k.py,sha256=vY3VCBmg61E8I4V-9-hJOv5RCYCmhxl6sHiKR9Zv7eE,5521
|
|
105
107
|
recce/tasks/valuediff.py,sha256=XJWkA307B5qTerT87fJRhJxPCqjmXn5eILYCXDfPtSQ,16439
|
|
106
108
|
recce/util/__init__.py,sha256=cDvL3WT32cJR9CUPIJmibwJoyYcB3ZaKBFbOZDyvqP8,95
|
|
107
|
-
recce/util/api_token.py,sha256=
|
|
109
|
+
recce/util/api_token.py,sha256=D1fM5romXnxXIvmedr8_0r_jspRRjWsmDK-lM7VSrk4,2737
|
|
108
110
|
recce/util/breaking.py,sha256=evT-xlDWMgIbuPQH6w5yNoDjOqopKzBXnCF9_xTn1S0,12591
|
|
109
111
|
recce/util/cache.py,sha256=QB6wzxe0M3jNTwP0M27Ys8F2hF-oda4-LyXXG9THuZQ,646
|
|
110
112
|
recce/util/cll.py,sha256=VXhtyoPIcPEW3_SmNUTbZWwOmmv8_JRfAB9CLJ4S2Wg,13315
|
|
111
113
|
recce/util/io.py,sha256=fNiIafNxPlTF6NNXlugzeJjfVGGrP3RXb39ERzRWD3Q,3459
|
|
112
|
-
recce/util/lineage.py,sha256=
|
|
114
|
+
recce/util/lineage.py,sha256=WdHxFTViqykBT8hBIbhGOovpEVnu9hbaW0VEQJUiCmE,2186
|
|
113
115
|
recce/util/logger.py,sha256=6UgLFkRiur9jJfu2ZRdo4LUvMw4f75V-l-1HT1-sgKo,747
|
|
116
|
+
recce/util/onboarding_state.py,sha256=kwFirKlfXdl5WFkR_nmilqGKFyELNcSPMqYq-by35fk,1991
|
|
114
117
|
recce/util/pydantic_model.py,sha256=KumKuyCjbTzEMsKLE4-b-eZfp0gLhYDdmVtw1-hxiJw,587
|
|
115
|
-
recce/util/recce_cloud.py,sha256=
|
|
118
|
+
recce/util/recce_cloud.py,sha256=YgRs9PhER3EhESaJInJo6mf3uwvnp0OavHBY1OYHPXw,7958
|
|
116
119
|
recce/util/singleton.py,sha256=1cU99I0f9tjuMQLMJyLsK1oK3fZJMsO5-TbRHAMXqds,627
|
|
117
120
|
recce/yaml/__init__.py,sha256=EgXYlFeJZchatUClRDXbIC5Oqb2_nBvB2NqItYVihio,1292
|
|
118
|
-
recce_nightly-1.9.0.
|
|
121
|
+
recce_nightly-1.9.0.20250624.dist-info/licenses/LICENSE,sha256=CQjjMy9aYPhfe8xG_bcpIfKtNkdxLZ5IOb8oPygtUhY,11343
|
|
119
122
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
120
123
|
tests/test_cli.py,sha256=_ikuZW4bYEGyj5A_V7nDSyBa1tVZqc0TVm-TJ0rEYVo,5467
|
|
121
124
|
tests/test_config.py,sha256=ODDFe_XF6gphmSmmc422dGLBaCCmG-IjDzTkD5SJsJE,1557
|
|
125
|
+
tests/test_connect_to_cloud.py,sha256=b2fgV8L1iQBdEwh6RumMsIIyYg7GtMOMRz1dvE3WRPg,3059
|
|
122
126
|
tests/test_core.py,sha256=jfn9dV-k89O2TywUYHmMTSdvyfBUips1ful1cIY2oFY,799
|
|
123
127
|
tests/test_dbt.py,sha256=VzXvdoJNwwEmKNhJJDNB1N_qZYwp1aoJQ1sLcoyRBmk,1316
|
|
124
128
|
tests/test_pull_request.py,sha256=HmZo5MoDaoKSgPwbLxJ3Ur3ajZ7IxhkzJxaOmhg6bwE,3562
|
|
@@ -130,7 +134,7 @@ tests/adapter/dbt_adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
130
134
|
tests/adapter/dbt_adapter/conftest.py,sha256=WlMVytJcyfLJlMBeYhpk8Q-nyCDR9x_7qgsy6Uh9ECw,386
|
|
131
135
|
tests/adapter/dbt_adapter/dbt_test_helper.py,sha256=o_7m0cnVeGwPwEFXBCx3Me5QqLzQMOBR3dXD-MS2eaM,10406
|
|
132
136
|
tests/adapter/dbt_adapter/test_dbt_adapter.py,sha256=Y5TSlqkXGjJC6hC7YYs45bnRht5aou_KLjPMjpAGxNM,830
|
|
133
|
-
tests/adapter/dbt_adapter/test_dbt_cll.py,sha256=
|
|
137
|
+
tests/adapter/dbt_adapter/test_dbt_cll.py,sha256=IvFIImI6SQF-F-Ti0tHvUEZkAiKOmdpb9ZeJ_5JOw9w,14294
|
|
134
138
|
tests/adapter/dbt_adapter/test_selector.py,sha256=uKFm0QxrxG-xyUEXGobCMueCRUssgnZMyo-S1Nlk3_s,6856
|
|
135
139
|
tests/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
136
140
|
tests/tasks/conftest.py,sha256=lyZFx4jEY3To3xVhfMQ-G45egYOCttKN2lxeyExACkY,157
|
|
@@ -143,8 +147,8 @@ tests/tasks/test_row_count.py,sha256=21PaP2aq-x8-pqwzWHRT1sixhQ8g3CQNRWOZTTmbK0s
|
|
|
143
147
|
tests/tasks/test_schema.py,sha256=7ds4Vx8ixaiIWDR49Lvjem4xlPkRP1cXazDRY3roUak,3121
|
|
144
148
|
tests/tasks/test_top_k.py,sha256=YR_GS__DJsbDlQVaEEdJvNQ3fh1VmV5Nb3G7lb0r6YM,1779
|
|
145
149
|
tests/tasks/test_valuediff.py,sha256=_xQJGgxsXoy2NYk_Z6Hsw2FlVh6zk2nN_iUueyRN1e8,2046
|
|
146
|
-
recce_nightly-1.9.0.
|
|
147
|
-
recce_nightly-1.9.0.
|
|
148
|
-
recce_nightly-1.9.0.
|
|
149
|
-
recce_nightly-1.9.0.
|
|
150
|
-
recce_nightly-1.9.0.
|
|
150
|
+
recce_nightly-1.9.0.20250624.dist-info/METADATA,sha256=okPHlV3RrR0VRdIJj1C7cXeoRRADck7xIb4fhNbsuDc,9397
|
|
151
|
+
recce_nightly-1.9.0.20250624.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
152
|
+
recce_nightly-1.9.0.20250624.dist-info/entry_points.txt,sha256=oqoY_IiwIqXbgrIsPnlqUqao2eiIeP2dprowkOlmeyg,40
|
|
153
|
+
recce_nightly-1.9.0.20250624.dist-info/top_level.txt,sha256=6PKGVpf75idP0C6KEaldDzzZUauIxNu1ZDstau1pI4I,12
|
|
154
|
+
recce_nightly-1.9.0.20250624.dist-info/RECORD,,
|
|
@@ -51,11 +51,11 @@ def test_cll_basic(dbt_test_helper):
|
|
|
51
51
|
)
|
|
52
52
|
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
53
53
|
|
|
54
|
-
result = adapter.
|
|
54
|
+
result = adapter.get_cll("model.model2", no_filter=True)
|
|
55
55
|
assert_model(result, "model.model2", [("model.model1", "c")])
|
|
56
56
|
assert_column(result, "model.model2", "c", "passthrough", [("model.model1", "c")])
|
|
57
57
|
|
|
58
|
-
result = adapter.
|
|
58
|
+
result = adapter.get_cll("model.model3", no_filter=True)
|
|
59
59
|
assert_model(result, "model.model3", [("model.model1", "c")])
|
|
60
60
|
assert_column(result, "model.model3", "c", "passthrough", [("model.model1", "c")])
|
|
61
61
|
|
|
@@ -75,7 +75,7 @@ def test_cll_table_alisa(dbt_test_helper):
|
|
|
75
75
|
depends_on=["model.model1"],
|
|
76
76
|
)
|
|
77
77
|
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
78
|
-
result = adapter.
|
|
78
|
+
result = adapter.get_cll("model.model1", no_filter=True)
|
|
79
79
|
assert_column(result, "model.model2", "c", "passthrough", [("model.model1", "c")])
|
|
80
80
|
|
|
81
81
|
|
|
@@ -103,7 +103,7 @@ def test_seed(dbt_test_helper):
|
|
|
103
103
|
)
|
|
104
104
|
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
105
105
|
|
|
106
|
-
result = adapter.
|
|
106
|
+
result = adapter.get_cll("model.model1", no_filter=True)
|
|
107
107
|
assert_model(result, "seed.seed1", [])
|
|
108
108
|
assert_column(result, "seed.seed1", "customer_id", "source", [])
|
|
109
109
|
assert_model(result, "model.model1", [("seed.seed1", "age")])
|
|
@@ -134,7 +134,7 @@ def test_python_model(dbt_test_helper):
|
|
|
134
134
|
assert not adapter.is_python_model("model1")
|
|
135
135
|
assert adapter.is_python_model("model2")
|
|
136
136
|
|
|
137
|
-
result = adapter.
|
|
137
|
+
result = adapter.get_cll("model1", no_filter=True)
|
|
138
138
|
assert_column(result, "model2", "customer_id", "unknown", [])
|
|
139
139
|
|
|
140
140
|
|
|
@@ -161,9 +161,9 @@ def test_source(dbt_test_helper):
|
|
|
161
161
|
depends_on=["source.source1.table1"],
|
|
162
162
|
)
|
|
163
163
|
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
164
|
-
result = adapter.
|
|
164
|
+
result = adapter.get_cll("source.source1.table1", no_filter=True)
|
|
165
165
|
assert_column(result, "source.source1.table1", "customer_id", "source", [])
|
|
166
|
-
result = adapter.
|
|
166
|
+
result = adapter.get_cll("model.model1", no_filter=True)
|
|
167
167
|
assert_column(result, "model.model1", "customer_id", "passthrough", [("source.source1.table1", "customer_id")])
|
|
168
168
|
|
|
169
169
|
|
|
@@ -171,14 +171,14 @@ def test_parse_error(dbt_test_helper):
|
|
|
171
171
|
dbt_test_helper.create_model("model1", curr_sql="select 1 as c", curr_columns={"c": "int"})
|
|
172
172
|
dbt_test_helper.create_model("model2", curr_sql="this is not a valid sql", curr_columns={"c": "int"})
|
|
173
173
|
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
174
|
-
result = adapter.
|
|
174
|
+
result = adapter.get_cll("model2", no_filter=True)
|
|
175
175
|
assert_column(result, "model2", "c", "unknown", [])
|
|
176
176
|
|
|
177
177
|
|
|
178
178
|
def test_model_without_catalog(dbt_test_helper):
|
|
179
179
|
dbt_test_helper.create_model("model1", curr_sql="select 1 as c")
|
|
180
180
|
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
181
|
-
result = adapter.
|
|
181
|
+
result = adapter.get_cll("model1", no_filter=True)
|
|
182
182
|
assert not result.nodes["model1"].columns
|
|
183
183
|
|
|
184
184
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
|
|
6
|
+
from cryptography.hazmat.primitives import hashes
|
|
7
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
8
|
+
|
|
9
|
+
from recce.connect_to_cloud import (
|
|
10
|
+
connect_to_cloud_background_task,
|
|
11
|
+
decrypt_code,
|
|
12
|
+
generate_key_pair,
|
|
13
|
+
is_callback_server_running,
|
|
14
|
+
prepare_connection_url,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConnectToCloudTests(unittest.TestCase):
|
|
19
|
+
|
|
20
|
+
def test_generate_key_pair(self):
|
|
21
|
+
private_key, public_key = generate_key_pair()
|
|
22
|
+
self.assertIsNotNone(private_key)
|
|
23
|
+
self.assertIsNotNone(public_key)
|
|
24
|
+
self.assertEqual(private_key.public_key().public_numbers(), public_key.public_numbers())
|
|
25
|
+
|
|
26
|
+
def test_prepare_connection_url(self):
|
|
27
|
+
_, public_key = generate_key_pair()
|
|
28
|
+
url, port = prepare_connection_url(public_key)
|
|
29
|
+
self.assertIn("connect?", url)
|
|
30
|
+
self.assertTrue(port >= 10000 and port <= 15000)
|
|
31
|
+
|
|
32
|
+
def test_decrypt_code(self):
|
|
33
|
+
private_key, public_key = generate_key_pair()
|
|
34
|
+
test_string = "recce-api-token-123"
|
|
35
|
+
ciphertext = public_key.encrypt(
|
|
36
|
+
test_string.encode(),
|
|
37
|
+
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None),
|
|
38
|
+
)
|
|
39
|
+
b64_ciphertext = base64.b64encode(ciphertext).decode()
|
|
40
|
+
result = decrypt_code(private_key, b64_ciphertext)
|
|
41
|
+
self.assertEqual(result, test_string)
|
|
42
|
+
|
|
43
|
+
@patch("recce.connect_to_cloud.update_recce_api_token")
|
|
44
|
+
@patch("recce.connect_to_cloud.update_onboarding_state")
|
|
45
|
+
@patch("recce.connect_to_cloud.RecceCloud")
|
|
46
|
+
def test_handle_callback_request_success(self, mock_recce_cloud, mock_update_state, mock_update_token):
|
|
47
|
+
private_key, public_key = generate_key_pair()
|
|
48
|
+
|
|
49
|
+
# Prepare encrypted token
|
|
50
|
+
test_token = "recce-api-token-xyz"
|
|
51
|
+
ciphertext = public_key.encrypt(
|
|
52
|
+
test_token.encode(),
|
|
53
|
+
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None),
|
|
54
|
+
)
|
|
55
|
+
encrypted_b64 = base64.b64encode(ciphertext).decode()
|
|
56
|
+
|
|
57
|
+
# Set up mocks
|
|
58
|
+
mock_recce_cloud.return_value.verify_token.return_value = True
|
|
59
|
+
|
|
60
|
+
from recce.connect_to_cloud import handle_callback_request
|
|
61
|
+
|
|
62
|
+
result = handle_callback_request(f"code={quote(encrypted_b64)}", private_key)
|
|
63
|
+
|
|
64
|
+
assert result == test_token
|
|
65
|
+
mock_update_token.assert_called_once_with(test_token)
|
|
66
|
+
mock_update_state.assert_called_once_with(test_token, False)
|
|
67
|
+
|
|
68
|
+
def test_is_callback_server_running(self):
|
|
69
|
+
# Should return False by default
|
|
70
|
+
self.assertFalse(is_callback_server_running())
|
|
71
|
+
|
|
72
|
+
@patch("recce.connect_to_cloud.run_one_time_http_server")
|
|
73
|
+
def test_connect_to_cloud_background_task_runs(self, mock_server):
|
|
74
|
+
private_key, public_key = generate_key_pair()
|
|
75
|
+
url, port = prepare_connection_url(public_key)
|
|
76
|
+
|
|
77
|
+
connect_to_cloud_background_task(private_key, port, url)
|
|
78
|
+
mock_server.assert_called_once()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
unittest.main()
|