recce-nightly 1.10.0.20250629__py3-none-any.whl → 1.25.0.20251112a20664__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.
- recce/VERSION +1 -1
- recce/__init__.py +5 -0
- recce/adapter/dbt_adapter/__init__.py +116 -74
- recce/artifact.py +76 -3
- recce/cli.py +665 -69
- recce/config.py +2 -2
- recce/connect_to_cloud.py +1 -1
- recce/core.py +3 -3
- recce/data/404.html +1 -22
- recce/data/__next.__PAGE__.txt +10 -0
- recce/data/__next._full.txt +23 -0
- recce/data/__next._index.txt +8 -0
- recce/data/__next._tree.txt +12 -0
- recce/data/_next/static/JwV_pqetN5WamZZ7aGdfH/_buildManifest.js +11 -0
- recce/data/_next/static/JwV_pqetN5WamZZ7aGdfH/_clientMiddlewareManifest.json +1 -0
- recce/data/_next/static/chunks/0a2b2dd4b57049c2.js +1 -0
- recce/data/_next/static/chunks/19c10d219a6a21ff.js +1 -0
- recce/data/_next/static/chunks/24fd885c7180a612.js +1 -0
- recce/data/_next/static/chunks/27e66b2eab4adc32.js +19 -0
- recce/data/_next/static/chunks/67b1c6a62f19d429.js +110 -0
- recce/data/_next/static/chunks/71f88fcc615bf282.js +1 -0
- recce/data/_next/static/chunks/917619ab62a32388.js +1 -0
- recce/data/_next/static/chunks/93ba5a62932b704f.js +4 -0
- recce/data/_next/static/chunks/a43a2a5e06d5a92b.js +1 -0
- recce/data/_next/static/chunks/a6c78b24bd8b84fc.js +1 -0
- recce/data/_next/static/chunks/ba2d87265a68599d.css +2 -0
- recce/data/_next/static/chunks/c117fd1c1382dd83.js +11 -0
- recce/data/_next/static/chunks/c9425ca46eebdde9.js +1 -0
- recce/data/_next/static/chunks/cc8a9eadba012be0.css +6 -0
- recce/data/_next/static/chunks/e124bccf574a3361.css +1 -0
- recce/data/_next/static/chunks/e392ad92847c3e17.js +1 -0
- recce/data/_next/static/chunks/e4ce95efe88dae79.js +11 -0
- recce/data/_next/static/chunks/e69c777814fea6ed.js +2 -0
- recce/data/_next/static/chunks/turbopack-21cfd73037ff57ab.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/_not-found/__next._full.txt +17 -0
- recce/data/_not-found/__next._index.txt +8 -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 +10 -0
- recce/data/_not-found.html +1 -0
- recce/data/_not-found.txt +17 -0
- recce/data/auth_callback.html +1 -1
- recce/data/index.html +1 -27
- recce/data/index.txt +23 -8
- 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 +632 -0
- recce/models/types.py +23 -2
- recce/pull_request.py +1 -1
- recce/run.py +23 -16
- recce/server.py +165 -11
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +632 -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 +2 -1
- recce/tasks/dataframe.py +59 -2
- recce/tasks/rowcount.py +4 -1
- recce/tasks/schema.py +4 -1
- recce/tasks/valuediff.py +1 -1
- recce/util/api_token.py +11 -2
- recce/util/breaking.py +9 -0
- recce/util/cll.py +1 -2
- recce/util/io.py +2 -2
- recce/util/lineage.py +14 -18
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +229 -5
- recce/yaml/__init__.py +2 -2
- recce_cloud/__init__.py +15 -0
- recce_cloud/api/__init__.py +17 -0
- recce_cloud/api/base.py +104 -0
- recce_cloud/api/client.py +150 -0
- recce_cloud/api/exceptions.py +26 -0
- recce_cloud/api/factory.py +63 -0
- recce_cloud/api/github.py +72 -0
- recce_cloud/api/gitlab.py +78 -0
- recce_cloud/artifact.py +57 -0
- recce_cloud/ci_providers/__init__.py +9 -0
- recce_cloud/ci_providers/base.py +82 -0
- recce_cloud/ci_providers/detector.py +147 -0
- recce_cloud/ci_providers/github_actions.py +136 -0
- recce_cloud/ci_providers/gitlab_ci.py +130 -0
- recce_cloud/cli.py +303 -0
- recce_cloud/upload.py +213 -0
- {recce_nightly-1.10.0.20250629.dist-info → recce_nightly-1.25.0.20251112a20664.dist-info}/METADATA +31 -27
- recce_nightly-1.25.0.20251112a20664.dist-info/RECORD +178 -0
- {recce_nightly-1.10.0.20250629.dist-info → recce_nightly-1.25.0.20251112a20664.dist-info}/top_level.txt +1 -0
- tests/adapter/dbt_adapter/test_dbt_cll.py +68 -17
- tests/recce_cloud/__init__.py +0 -0
- tests/recce_cloud/test_ci_providers.py +351 -0
- tests/recce_cloud/test_cli.py +372 -0
- tests/recce_cloud/test_client.py +273 -0
- tests/recce_cloud/test_platform_clients.py +279 -0
- tests/test_cli.py +106 -3
- tests/test_cli_mcp_optional.py +45 -0
- tests/test_cloud_listing_cli.py +324 -0
- tests/test_core.py +147 -0
- tests/test_mcp_server.py +332 -0
- tests/test_server.py +6 -6
- tests/test_summary.py +14 -6
- recce/data/_next/static/Mrb9CZ3toH6Q8xrzNzCrg/_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/41-f30276c289169376.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-68460b15fe448f33.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-292f035bb0d2a98e.js +0 -1
- recce/data/_next/static/chunks/app/page-598f8acc82179d01.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/35c6679a098e1e34.css +0 -1
- recce/data/_next/static/css/951e2e0eea2d4a5b.css +0 -14
- recce/data/_next/static/css/a2b12b4ba4227f0a.css +0 -3
- 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/state.py +0 -786
- recce_nightly-1.10.0.20250629.dist-info/RECORD +0 -154
- tests/test_state.py +0 -134
- /recce/data/_next/static/{Mrb9CZ3toH6Q8xrzNzCrg → JwV_pqetN5WamZZ7aGdfH}/_ssgManifest.js +0 -0
- /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/media/{reload-image.79aabb7d.svg → reload-image.7aa931c7.svg} +0 -0
- {recce_nightly-1.10.0.20250629.dist-info → recce_nightly-1.25.0.20251112a20664.dist-info}/WHEEL +0 -0
- {recce_nightly-1.10.0.20250629.dist-info → recce_nightly-1.25.0.20251112a20664.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.10.0.20250629.dist-info → recce_nightly-1.25.0.20251112a20664.dist-info}/licenses/LICENSE +0 -0
recce/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.25.0.20251112a20664
|
recce/__init__.py
CHANGED
|
@@ -35,6 +35,11 @@ def is_ci_env():
|
|
|
35
35
|
return False
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def is_recce_cloud_instance():
|
|
39
|
+
"""Check if running in Recce Cloud instance."""
|
|
40
|
+
return os.environ.get("RECCE_CLOUD_INSTANCE", "false").lower() == "true"
|
|
41
|
+
|
|
42
|
+
|
|
38
43
|
def get_runner():
|
|
39
44
|
# GitHub Action
|
|
40
45
|
if os.environ.get("GITHUB_ACTIONS", "false") == "true":
|
|
@@ -31,9 +31,10 @@ from recce.util.lineage import (
|
|
|
31
31
|
find_downstream,
|
|
32
32
|
find_upstream,
|
|
33
33
|
)
|
|
34
|
+
from recce.util.perf_tracking import LineagePerfTracker
|
|
34
35
|
|
|
35
36
|
from ...tasks.profile import ProfileTask
|
|
36
|
-
from ...util.breaking import parse_change_category
|
|
37
|
+
from ...util.breaking import BreakingPerformanceTracking, parse_change_category
|
|
37
38
|
|
|
38
39
|
try:
|
|
39
40
|
import agate
|
|
@@ -278,7 +279,7 @@ class DbtArgs:
|
|
|
278
279
|
target_path: Optional[str] = (None,)
|
|
279
280
|
project_only_flags: Optional[Dict[str, Any]] = None
|
|
280
281
|
which: Optional[str] = None
|
|
281
|
-
state_modified_compare_more_unrendered_values: Optional[bool] =
|
|
282
|
+
state_modified_compare_more_unrendered_values: Optional[bool] = True # new flag added since dbt v1.9
|
|
282
283
|
|
|
283
284
|
|
|
284
285
|
@dataclass
|
|
@@ -407,7 +408,7 @@ class DbtAdapter(BaseAdapter):
|
|
|
407
408
|
|
|
408
409
|
if self.adapter.connections.TYPE == "databricks":
|
|
409
410
|
# reference: get_columns_in_relation (dbt/adapters/databricks/impl.py)
|
|
410
|
-
from dbt.adapters.databricks import DatabricksColumn
|
|
411
|
+
from dbt.adapters.databricks.column import DatabricksColumn
|
|
411
412
|
|
|
412
413
|
rows = columns
|
|
413
414
|
columns = []
|
|
@@ -599,7 +600,15 @@ class DbtAdapter(BaseAdapter):
|
|
|
599
600
|
return node.compiled_code
|
|
600
601
|
else:
|
|
601
602
|
from dbt.clients import jinja
|
|
602
|
-
from dbt.context.providers import
|
|
603
|
+
from dbt.context.providers import (
|
|
604
|
+
generate_runtime_macro_context,
|
|
605
|
+
generate_runtime_model_context,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# Set up macro resolver for dbt >= 1.8
|
|
609
|
+
macro_manifest = MacroManifest(manifest.macros)
|
|
610
|
+
self.adapter.set_macro_resolver(macro_manifest)
|
|
611
|
+
self.adapter.set_macro_context_generator(generate_runtime_macro_context)
|
|
603
612
|
|
|
604
613
|
jinja_ctx = generate_runtime_model_context(node, self.runtime_config, manifest)
|
|
605
614
|
jinja_ctx.update(context)
|
|
@@ -658,8 +667,8 @@ class DbtAdapter(BaseAdapter):
|
|
|
658
667
|
@lru_cache(maxsize=2)
|
|
659
668
|
def get_lineage_cached(self, base: Optional[bool] = False, cache_key=0):
|
|
660
669
|
if base is False:
|
|
661
|
-
|
|
662
|
-
|
|
670
|
+
perf_tracker = LineagePerfTracker()
|
|
671
|
+
perf_tracker.start_lineage()
|
|
663
672
|
|
|
664
673
|
manifest = self.curr_manifest if base is False else self.base_manifest
|
|
665
674
|
catalog = self.curr_catalog if base is False else self.base_catalog
|
|
@@ -736,6 +745,7 @@ class DbtAdapter(BaseAdapter):
|
|
|
736
745
|
nodes[unique_id] = {
|
|
737
746
|
"id": source["unique_id"],
|
|
738
747
|
"name": source["name"],
|
|
748
|
+
"source_name": source["source_name"],
|
|
739
749
|
"resource_type": source["resource_type"],
|
|
740
750
|
"package_name": source["package_name"],
|
|
741
751
|
"config": source["config"],
|
|
@@ -777,10 +787,10 @@ class DbtAdapter(BaseAdapter):
|
|
|
777
787
|
parent_map = self.build_parent_map(nodes, base)
|
|
778
788
|
|
|
779
789
|
if base is False:
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
log_performance("model lineage",
|
|
783
|
-
|
|
790
|
+
perf_tracker.end_lineage()
|
|
791
|
+
perf_tracker.set_total_nodes(len(nodes))
|
|
792
|
+
log_performance("model lineage", perf_tracker.to_dict())
|
|
793
|
+
perf_tracker.reset()
|
|
784
794
|
|
|
785
795
|
return dict(
|
|
786
796
|
parent_map=parent_map,
|
|
@@ -814,17 +824,22 @@ class DbtAdapter(BaseAdapter):
|
|
|
814
824
|
|
|
815
825
|
@lru_cache(maxsize=128)
|
|
816
826
|
def get_change_analysis_cached(self, node_id: str):
|
|
827
|
+
breaking_perf_tracker = BreakingPerformanceTracking()
|
|
817
828
|
lineage_diff = self.get_lineage_diff()
|
|
818
829
|
diff = lineage_diff.diff
|
|
819
830
|
|
|
820
831
|
if node_id not in diff or diff[node_id].change_status != "modified":
|
|
821
832
|
return diff.get(node_id)
|
|
822
833
|
|
|
834
|
+
breaking_perf_tracker.increment_modified_nodes()
|
|
835
|
+
breaking_perf_tracker.start_lineage_diff()
|
|
836
|
+
|
|
823
837
|
base = lineage_diff.base
|
|
824
838
|
current = lineage_diff.current
|
|
825
839
|
|
|
826
840
|
base_manifest = as_manifest(self.get_manifest(True))
|
|
827
841
|
curr_manifest = as_manifest(self.get_manifest(False))
|
|
842
|
+
breaking_perf_tracker.record_checkpoint("manifest")
|
|
828
843
|
|
|
829
844
|
def ref_func(*args):
|
|
830
845
|
if len(args) == 1:
|
|
@@ -895,6 +910,7 @@ class DbtAdapter(BaseAdapter):
|
|
|
895
910
|
old_schema=base_schema,
|
|
896
911
|
new_schema=curr_schema,
|
|
897
912
|
dialect=dialect,
|
|
913
|
+
perf_tracking=breaking_perf_tracker,
|
|
898
914
|
)
|
|
899
915
|
|
|
900
916
|
# Make sure that the case of the column names are the same
|
|
@@ -917,6 +933,9 @@ class DbtAdapter(BaseAdapter):
|
|
|
917
933
|
# TODO: telemetry
|
|
918
934
|
pass
|
|
919
935
|
|
|
936
|
+
breaking_perf_tracker.end_lineage_diff()
|
|
937
|
+
log_performance("change analysis per node", breaking_perf_tracker.to_dict())
|
|
938
|
+
breaking_perf_tracker.reset()
|
|
920
939
|
node_diff = diff.get(node_id)
|
|
921
940
|
node_diff.change = change
|
|
922
941
|
return node_diff
|
|
@@ -931,7 +950,15 @@ class DbtAdapter(BaseAdapter):
|
|
|
931
950
|
no_downstream: Optional[bool] = False,
|
|
932
951
|
no_filter: Optional[bool] = False,
|
|
933
952
|
) -> CllData:
|
|
934
|
-
cll_tracker =
|
|
953
|
+
cll_tracker = LineagePerfTracker()
|
|
954
|
+
cll_tracker.set_params(
|
|
955
|
+
has_node=node_id is not None,
|
|
956
|
+
has_column=column is not None,
|
|
957
|
+
change_analysis=change_analysis,
|
|
958
|
+
no_cll=no_cll,
|
|
959
|
+
no_upstream=no_upstream,
|
|
960
|
+
no_downstream=no_downstream,
|
|
961
|
+
)
|
|
935
962
|
cll_tracker.start_column_lineage()
|
|
936
963
|
|
|
937
964
|
manifest = self.curr_manifest
|
|
@@ -944,6 +971,8 @@ class DbtAdapter(BaseAdapter):
|
|
|
944
971
|
lineage_diff = self.get_lineage_diff()
|
|
945
972
|
cll_node_ids = set(lineage_diff.diff.keys())
|
|
946
973
|
|
|
974
|
+
cll_tracker.set_init_nodes(len(cll_node_ids))
|
|
975
|
+
|
|
947
976
|
nodes = {}
|
|
948
977
|
columns = {}
|
|
949
978
|
parent_map = {}
|
|
@@ -955,29 +984,41 @@ class DbtAdapter(BaseAdapter):
|
|
|
955
984
|
cll_node_ids = cll_node_ids.union(find_downstream(cll_node_ids, manifest_dict.get("child_map")))
|
|
956
985
|
|
|
957
986
|
if not no_cll:
|
|
987
|
+
allowed_related_nodes = set()
|
|
988
|
+
for key in ["sources", "nodes", "exposures", "metrics"]:
|
|
989
|
+
attr = getattr(manifest, key)
|
|
990
|
+
allowed_related_nodes.update(set(attr.keys()))
|
|
991
|
+
if hasattr(manifest, "semantic_models"):
|
|
992
|
+
attr = getattr(manifest, "semantic_models")
|
|
993
|
+
allowed_related_nodes.update(set(attr.keys()))
|
|
958
994
|
for cll_node_id in cll_node_ids:
|
|
959
|
-
if
|
|
960
|
-
cll_node_id not in manifest.sources
|
|
961
|
-
and cll_node_id not in manifest.nodes
|
|
962
|
-
and cll_node_id not in manifest.exposures
|
|
963
|
-
):
|
|
995
|
+
if cll_node_id not in allowed_related_nodes:
|
|
964
996
|
continue
|
|
965
997
|
cll_data_one = deepcopy(self.get_cll_cached(cll_node_id, base=False))
|
|
998
|
+
cll_tracker.increment_cll_nodes()
|
|
966
999
|
if cll_data_one is None:
|
|
967
1000
|
continue
|
|
968
1001
|
|
|
969
1002
|
nodes[cll_node_id] = cll_data_one.nodes.get(cll_node_id)
|
|
970
|
-
node_diff =
|
|
1003
|
+
node_diff = None
|
|
1004
|
+
if change_analysis:
|
|
1005
|
+
node_diff = self.get_change_analysis_cached(cll_node_id)
|
|
1006
|
+
cll_tracker.increment_change_analysis_nodes()
|
|
971
1007
|
if node_diff is not None:
|
|
972
1008
|
nodes[cll_node_id].change_status = node_diff.change_status
|
|
973
1009
|
if node_diff.change is not None:
|
|
974
1010
|
nodes[cll_node_id].change_category = node_diff.change.category
|
|
975
1011
|
for c_id, c in cll_data_one.columns.items():
|
|
976
1012
|
columns[c_id] = c
|
|
977
|
-
if node_diff is not None
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1013
|
+
if node_diff is not None:
|
|
1014
|
+
if node_diff.change_status == "added":
|
|
1015
|
+
c.change_status = "added"
|
|
1016
|
+
elif node_diff.change_status == "removed":
|
|
1017
|
+
c.change_status = "removed"
|
|
1018
|
+
elif node_diff.change is not None and node_diff.change.columns is not None:
|
|
1019
|
+
column_diff = node_diff.change.columns.get(c.name)
|
|
1020
|
+
if column_diff:
|
|
1021
|
+
c.change_status = column_diff
|
|
981
1022
|
|
|
982
1023
|
for p_id, parents in cll_data_one.parent_map.items():
|
|
983
1024
|
parent_map[p_id] = parents
|
|
@@ -987,13 +1028,7 @@ class DbtAdapter(BaseAdapter):
|
|
|
987
1028
|
cll_node_columns: Dict[str, CllColumn] = {}
|
|
988
1029
|
|
|
989
1030
|
if cll_node_id in manifest.sources:
|
|
990
|
-
|
|
991
|
-
cll_node = CllNode(
|
|
992
|
-
id=n.unique_id,
|
|
993
|
-
name=n.name,
|
|
994
|
-
source_name=n.source_name,
|
|
995
|
-
package_name=n.package_name,
|
|
996
|
-
)
|
|
1031
|
+
cll_node = CllNode.build_cll_node(manifest, "sources", cll_node_id)
|
|
997
1032
|
if self.curr_catalog and cll_node_id in self.curr_catalog.sources:
|
|
998
1033
|
cll_node_columns = {
|
|
999
1034
|
column.name: CllColumn(
|
|
@@ -1005,15 +1040,7 @@ class DbtAdapter(BaseAdapter):
|
|
|
1005
1040
|
for column in self.curr_catalog.sources[cll_node_id].columns.values()
|
|
1006
1041
|
}
|
|
1007
1042
|
elif cll_node_id in manifest.nodes:
|
|
1008
|
-
|
|
1009
|
-
if n.resource_type not in ["model", "seed", "snapshot"]:
|
|
1010
|
-
continue
|
|
1011
|
-
cll_node = CllNode(
|
|
1012
|
-
id=n.unique_id,
|
|
1013
|
-
name=n.name,
|
|
1014
|
-
package_name=n.package_name,
|
|
1015
|
-
resource_type=n.resource_type,
|
|
1016
|
-
)
|
|
1043
|
+
cll_node = CllNode.build_cll_node(manifest, "nodes", cll_node_id)
|
|
1017
1044
|
if self.curr_catalog and cll_node_id in self.curr_catalog.nodes:
|
|
1018
1045
|
cll_node_columns = {
|
|
1019
1046
|
column.name: CllColumn(
|
|
@@ -1025,19 +1052,20 @@ class DbtAdapter(BaseAdapter):
|
|
|
1025
1052
|
for column in self.curr_catalog.nodes[cll_node_id].columns.values()
|
|
1026
1053
|
}
|
|
1027
1054
|
elif cll_node_id in manifest.exposures:
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
resource_type=n.resource_type,
|
|
1034
|
-
)
|
|
1055
|
+
cll_node = CllNode.build_cll_node(manifest, "exposures", cll_node_id)
|
|
1056
|
+
elif hasattr(manifest, "semantic_models") and cll_node_id in manifest.semantic_models:
|
|
1057
|
+
cll_node = CllNode.build_cll_node(manifest, "semantic_models", cll_node_id)
|
|
1058
|
+
elif cll_node_id in manifest.metrics:
|
|
1059
|
+
cll_node = CllNode.build_cll_node(manifest, "metrics", cll_node_id)
|
|
1035
1060
|
|
|
1036
1061
|
if not cll_node:
|
|
1037
1062
|
continue
|
|
1038
1063
|
nodes[cll_node_id] = cll_node
|
|
1039
1064
|
|
|
1040
|
-
node_diff =
|
|
1065
|
+
node_diff = None
|
|
1066
|
+
if change_analysis:
|
|
1067
|
+
node_diff = self.get_change_analysis_cached(cll_node_id)
|
|
1068
|
+
cll_tracker.increment_change_analysis_nodes()
|
|
1041
1069
|
if node_diff is not None:
|
|
1042
1070
|
cll_node.change_status = node_diff.change_status
|
|
1043
1071
|
if node_diff.change is not None:
|
|
@@ -1063,7 +1091,19 @@ class DbtAdapter(BaseAdapter):
|
|
|
1063
1091
|
if node_id is None and column is None:
|
|
1064
1092
|
if change_analysis:
|
|
1065
1093
|
# If change analysis is requested, we need to find the nodes that have changes
|
|
1066
|
-
|
|
1094
|
+
lineage_diff = self.get_lineage_diff()
|
|
1095
|
+
for nid, nd in lineage_diff.diff.items():
|
|
1096
|
+
if nd.change_status == "added":
|
|
1097
|
+
anchor_node_ids.add(nid)
|
|
1098
|
+
n = lineage_diff.current["nodes"].get(nid)
|
|
1099
|
+
n_columns = n.get("columns", {})
|
|
1100
|
+
for c in n_columns:
|
|
1101
|
+
anchor_node_ids.add(build_column_key(nid, c))
|
|
1102
|
+
continue
|
|
1103
|
+
if nd.change_status == "removed":
|
|
1104
|
+
extra_node_ids.add(nid)
|
|
1105
|
+
continue
|
|
1106
|
+
|
|
1067
1107
|
node_diff = self.get_change_analysis_cached(nid)
|
|
1068
1108
|
if node_diff is not None and node_diff.change is not None:
|
|
1069
1109
|
extra_node_ids.add(nid)
|
|
@@ -1107,6 +1147,7 @@ class DbtAdapter(BaseAdapter):
|
|
|
1107
1147
|
else:
|
|
1108
1148
|
anchor_node_ids.add(f"{node_id}_{column}")
|
|
1109
1149
|
|
|
1150
|
+
cll_tracker.set_anchor_nodes(len(anchor_node_ids))
|
|
1110
1151
|
result_node_ids = set(anchor_node_ids)
|
|
1111
1152
|
if not no_upstream:
|
|
1112
1153
|
result_node_ids = result_node_ids.union(find_upstream(anchor_node_ids, parent_map))
|
|
@@ -1122,10 +1163,14 @@ class DbtAdapter(BaseAdapter):
|
|
|
1122
1163
|
node.columns = {
|
|
1123
1164
|
k: v for k, v in node.columns.items() if v.id in result_node_ids or v.id in extra_node_ids
|
|
1124
1165
|
}
|
|
1166
|
+
|
|
1167
|
+
if change_analysis:
|
|
1168
|
+
node.impacted = node.id in result_node_ids
|
|
1169
|
+
|
|
1125
1170
|
parent_map, child_map = filter_dependency_maps(parent_map, child_map, result_node_ids)
|
|
1126
1171
|
|
|
1127
1172
|
cll_tracker.end_column_lineage()
|
|
1128
|
-
cll_tracker.set_total_nodes(len(nodes))
|
|
1173
|
+
cll_tracker.set_total_nodes(len(nodes) + len(columns))
|
|
1129
1174
|
log_performance("column level lineage", cll_tracker.to_dict())
|
|
1130
1175
|
cll_tracker.reset()
|
|
1131
1176
|
|
|
@@ -1144,6 +1189,9 @@ class DbtAdapter(BaseAdapter):
|
|
|
1144
1189
|
if node is None:
|
|
1145
1190
|
return None
|
|
1146
1191
|
|
|
1192
|
+
cll_tracker.set_total_nodes(1)
|
|
1193
|
+
cll_tracker.start_column_lineage()
|
|
1194
|
+
|
|
1147
1195
|
def _apply_all_columns(node: CllNode, transformation_type):
|
|
1148
1196
|
cll_data = CllData()
|
|
1149
1197
|
cll_data.nodes[node.id] = node
|
|
@@ -1268,6 +1316,10 @@ class DbtAdapter(BaseAdapter):
|
|
|
1268
1316
|
depends_on.add(parent_key)
|
|
1269
1317
|
column.transformation_type = c2c_map[name].transformation_type
|
|
1270
1318
|
cll_data.parent_map[column_id] = set(depends_on)
|
|
1319
|
+
|
|
1320
|
+
cll_tracker.end_column_lineage()
|
|
1321
|
+
log_performance("column level lineage per node", cll_tracker.to_dict())
|
|
1322
|
+
cll_tracker.reset()
|
|
1271
1323
|
return cll_data
|
|
1272
1324
|
|
|
1273
1325
|
def get_cll_node(self, node_id: str, base: Optional[bool] = False) -> Tuple[Optional[CllNode], list[str]]:
|
|
@@ -1279,21 +1331,12 @@ class DbtAdapter(BaseAdapter):
|
|
|
1279
1331
|
# model, seed, snapshot
|
|
1280
1332
|
if node_id in manifest.nodes:
|
|
1281
1333
|
found = manifest.nodes[node_id]
|
|
1282
|
-
if found.resource_type not in ["model", "seed", "snapshot"]:
|
|
1283
|
-
return None, []
|
|
1284
|
-
|
|
1285
1334
|
unique_id = found.unique_id
|
|
1286
|
-
node = CllNode(
|
|
1287
|
-
id=found.unique_id,
|
|
1288
|
-
name=found.name,
|
|
1289
|
-
package_name=found.package_name,
|
|
1290
|
-
resource_type=found.resource_type,
|
|
1291
|
-
raw_code=found.raw_code,
|
|
1292
|
-
)
|
|
1335
|
+
node = CllNode.build_cll_node(manifest, "nodes", node_id)
|
|
1293
1336
|
if hasattr(found.depends_on, "nodes"):
|
|
1294
1337
|
parent_list = found.depends_on.nodes
|
|
1295
1338
|
|
|
1296
|
-
if catalog is not None and unique_id in catalog.nodes:
|
|
1339
|
+
if catalog is not None and node is not None and unique_id in catalog.nodes:
|
|
1297
1340
|
columns = {}
|
|
1298
1341
|
for col_name, col_metadata in catalog.nodes[unique_id].columns.items():
|
|
1299
1342
|
column_id = f"{unique_id}_{col_name}"
|
|
@@ -1305,17 +1348,10 @@ class DbtAdapter(BaseAdapter):
|
|
|
1305
1348
|
if node_id in manifest.sources:
|
|
1306
1349
|
found = manifest.sources[node_id]
|
|
1307
1350
|
unique_id = found.unique_id
|
|
1308
|
-
|
|
1309
|
-
node = CllNode(
|
|
1310
|
-
id=found.unique_id,
|
|
1311
|
-
name=found.name,
|
|
1312
|
-
package_name=found.package_name,
|
|
1313
|
-
resource_type=found.resource_type,
|
|
1314
|
-
source_name=found.source_name,
|
|
1315
|
-
)
|
|
1351
|
+
node = CllNode.build_cll_node(manifest, "sources", node_id)
|
|
1316
1352
|
parent_list = []
|
|
1317
1353
|
|
|
1318
|
-
if catalog is not None and unique_id in catalog.sources:
|
|
1354
|
+
if catalog is not None and node is not None and unique_id in catalog.sources:
|
|
1319
1355
|
columns = {}
|
|
1320
1356
|
for col_name, col_metadata in catalog.sources[unique_id].columns.items():
|
|
1321
1357
|
column_id = f"{unique_id}_{col_name}"
|
|
@@ -1326,13 +1362,19 @@ class DbtAdapter(BaseAdapter):
|
|
|
1326
1362
|
# exposure
|
|
1327
1363
|
if node_id in manifest.exposures:
|
|
1328
1364
|
found = manifest.exposures[node_id]
|
|
1365
|
+
node = CllNode.build_cll_node(manifest, "exposures", node_id)
|
|
1366
|
+
if hasattr(found.depends_on, "nodes"):
|
|
1367
|
+
parent_list = found.depends_on.nodes
|
|
1329
1368
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1369
|
+
if hasattr(manifest, "semantic_models") and node_id in manifest.semantic_models:
|
|
1370
|
+
found = manifest.semantic_models[node_id]
|
|
1371
|
+
node = CllNode.build_cll_node(manifest, "semantic_models", node_id)
|
|
1372
|
+
if hasattr(found.depends_on, "nodes"):
|
|
1373
|
+
parent_list = found.depends_on.nodes
|
|
1374
|
+
|
|
1375
|
+
if node_id in manifest.metrics:
|
|
1376
|
+
found = manifest.metrics[node_id]
|
|
1377
|
+
node = CllNode.build_cll_node(manifest, "metrics", node_id)
|
|
1336
1378
|
if hasattr(found.depends_on, "nodes"):
|
|
1337
1379
|
parent_list = found.depends_on.nodes
|
|
1338
1380
|
|
|
@@ -1558,7 +1600,7 @@ class DbtAdapter(BaseAdapter):
|
|
|
1558
1600
|
if not os.path.isfile(path):
|
|
1559
1601
|
return None
|
|
1560
1602
|
|
|
1561
|
-
with open(path, "r") as f:
|
|
1603
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
1562
1604
|
json_content = f.read()
|
|
1563
1605
|
return json.loads(json_content)
|
|
1564
1606
|
|
recce/artifact.py
CHANGED
|
@@ -40,7 +40,7 @@ def verify_artifacts_path(target_path: str) -> bool:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def parse_dbt_version(file_path: str) -> str:
|
|
43
|
-
with open(file_path, "r") as f:
|
|
43
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
44
44
|
data = json.load(f)
|
|
45
45
|
|
|
46
46
|
dbt_version = data.get("metadata", {}).get("dbt_version", None)
|
|
@@ -80,6 +80,64 @@ def archive_artifacts(target_path: str) -> (str, str):
|
|
|
80
80
|
return artifacts_tar_gz_path, dbt_version
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def upload_artifacts_to_session(target_path: str, session_id: str, token: str, debug: bool = False):
|
|
84
|
+
"""Upload dbt artifacts to a specific session ID in Recce Cloud."""
|
|
85
|
+
console = Console()
|
|
86
|
+
if verify_artifacts_path(target_path) is False:
|
|
87
|
+
console.print(f"[[red]Error[/red]] Invalid target path: {target_path}")
|
|
88
|
+
console.print("Please provide a valid target path containing manifest.json and catalog.json.")
|
|
89
|
+
return 1
|
|
90
|
+
|
|
91
|
+
manifest_path = os.path.join(target_path, "manifest.json")
|
|
92
|
+
catalog_path = os.path.join(target_path, "catalog.json")
|
|
93
|
+
|
|
94
|
+
# get the adapter type from the manifest file
|
|
95
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
96
|
+
manifest_data = json.load(f)
|
|
97
|
+
adapter_type = manifest_data.get("metadata", {}).get("adapter_type")
|
|
98
|
+
if adapter_type is None:
|
|
99
|
+
raise Exception("Failed to parse adapter type from manifest.json")
|
|
100
|
+
|
|
101
|
+
recce_cloud = RecceCloud(token)
|
|
102
|
+
|
|
103
|
+
session = recce_cloud.get_session(session_id)
|
|
104
|
+
|
|
105
|
+
org_id = session.get("org_id")
|
|
106
|
+
if org_id is None:
|
|
107
|
+
raise Exception(f"Session ID {session_id} does not belong to any organization.")
|
|
108
|
+
|
|
109
|
+
project_id = session.get("project_id")
|
|
110
|
+
if project_id is None:
|
|
111
|
+
raise Exception(f"Session ID {session_id} does not belong to any project.")
|
|
112
|
+
|
|
113
|
+
# Get the presigned URL for uploading the artifacts using session ID
|
|
114
|
+
console.print(f'Uploading artifacts for session ID "{session_id}"')
|
|
115
|
+
presigned_urls = recce_cloud.get_upload_urls_by_session_id(org_id, project_id, session_id)
|
|
116
|
+
if debug:
|
|
117
|
+
console.rule("Debug information", style="blue")
|
|
118
|
+
console.print(f"Org ID: {org_id}")
|
|
119
|
+
console.print(f"Project ID: {project_id}")
|
|
120
|
+
console.print(f"Session ID: {session_id}")
|
|
121
|
+
console.print(f"Manifest path: {presigned_urls['manifest_url']}")
|
|
122
|
+
console.print(f"Catalog path: {presigned_urls['catalog_url']}")
|
|
123
|
+
console.print(f"Adapter type: {adapter_type}")
|
|
124
|
+
|
|
125
|
+
# Upload the compressed artifacts (no password needed for session uploads)
|
|
126
|
+
console.print(f'Uploading manifest from path "{manifest_path}"')
|
|
127
|
+
response = requests.put(presigned_urls["manifest_url"], data=open(manifest_path, "rb").read())
|
|
128
|
+
if response.status_code != 200 and response.status_code != 204:
|
|
129
|
+
raise Exception(response.text)
|
|
130
|
+
console.print(f'Uploading catalog from path "{catalog_path}"')
|
|
131
|
+
response = requests.put(presigned_urls["catalog_url"], data=open(catalog_path, "rb").read())
|
|
132
|
+
if response.status_code != 200 and response.status_code != 204:
|
|
133
|
+
raise Exception(response.text)
|
|
134
|
+
|
|
135
|
+
# Update the session metadata
|
|
136
|
+
recce_cloud.update_session(org_id, project_id, session_id, adapter_type)
|
|
137
|
+
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
|
|
83
141
|
def upload_dbt_artifacts(target_path: str, branch: str, token: str, password: str, debug: bool = False):
|
|
84
142
|
console = Console()
|
|
85
143
|
if verify_artifacts_path(target_path) is False:
|
|
@@ -100,7 +158,7 @@ def upload_dbt_artifacts(target_path: str, branch: str, token: str, password: st
|
|
|
100
158
|
metadata = {"commit": sha, "dbt_version": dbt_version}
|
|
101
159
|
|
|
102
160
|
# Get the presigned URL for uploading the artifacts
|
|
103
|
-
presigned_url = RecceCloud(token).
|
|
161
|
+
presigned_url = RecceCloud(token).get_presigned_url_by_github_repo(
|
|
104
162
|
method=PresignedUrlMethod.UPLOAD,
|
|
105
163
|
repository=repo,
|
|
106
164
|
artifact_name="dbt_artifacts.tar.gz",
|
|
@@ -145,7 +203,7 @@ def download_dbt_artifacts(
|
|
|
145
203
|
sha = None
|
|
146
204
|
dbt_version = None
|
|
147
205
|
|
|
148
|
-
presigned_url, tags = RecceCloud(token).
|
|
206
|
+
presigned_url, tags = RecceCloud(token).get_download_presigned_url_by_github_repo_with_tags(
|
|
149
207
|
repository=repo,
|
|
150
208
|
artifact_name="dbt_artifacts.tar.gz",
|
|
151
209
|
branch=branch,
|
|
@@ -191,3 +249,18 @@ def download_dbt_artifacts(
|
|
|
191
249
|
except FileNotFoundError:
|
|
192
250
|
pass
|
|
193
251
|
return 0
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def delete_dbt_artifacts(branch: str, token: str, debug: bool = False):
|
|
255
|
+
"""Delete dbt artifacts from a specific branch in Recce Cloud."""
|
|
256
|
+
console = Console()
|
|
257
|
+
repo = hosting_repo()
|
|
258
|
+
|
|
259
|
+
if debug:
|
|
260
|
+
console.rule("Debug information", style="blue")
|
|
261
|
+
console.print(f"Git Branch: {branch}")
|
|
262
|
+
console.print(f"GitHub repository: {repo}")
|
|
263
|
+
|
|
264
|
+
console.print(f'Deleting dbt artifacts from branch: "{branch}"')
|
|
265
|
+
|
|
266
|
+
RecceCloud(token).purge_artifacts(repo, branch=branch)
|