recce-nightly 1.10.0.20250625__py3-none-any.whl → 1.30.0.20251221__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +5 -0
- recce/adapter/dbt_adapter/__init__.py +343 -245
- recce/apis/check_api.py +20 -14
- recce/apis/check_events_api.py +353 -0
- recce/apis/check_func.py +5 -5
- recce/apis/run_func.py +32 -3
- recce/artifact.py +76 -3
- recce/cli.py +705 -82
- recce/config.py +2 -2
- recce/connect_to_cloud.py +1 -1
- recce/core.py +3 -3
- recce/data/404/index.html +2 -0
- recce/data/404.html +2 -22
- recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
- recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
- recce/data/__next.__PAGE__.txt +6 -0
- recce/data/__next._full.txt +32 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +14 -0
- recce/data/__next._tree.txt +8 -0
- recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
- recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
- recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
- recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
- recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
- recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
- recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
- recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
- recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
- recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
- recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
- recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
- recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
- recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
- recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
- recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
- recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
- recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
- recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
- recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
- recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
- recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
- recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
- recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
- recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
- recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
- recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
- recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
- recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
- recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
- recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
- recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
- recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
- recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
- recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
- recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
- recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/{montserrat-cyrillic-800-normal.bd5c9f50.woff → montserrat-cyrillic-800-normal.f9d58125.woff} +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/{montserrat-latin-800-normal.fc315020.woff → montserrat-latin-800-normal.d5761935.woff} +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/{montserrat-latin-ext-800-normal.2e5381b2.woff → montserrat-latin-ext-800-normal.b671449b.woff} +0 -0
- recce/data/_next/static/media/{montserrat-vietnamese-800-normal.20c545e6.woff → montserrat-vietnamese-800-normal.9f7b8541.woff} +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
- recce/data/_not-found/__next._full.txt +24 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +13 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +6 -0
- recce/data/_not-found/index.html +2 -0
- recce/data/_not-found/index.txt +24 -0
- recce/data/auth_callback.html +1 -1
- recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/checks/__next._full.txt +39 -0
- recce/data/checks/__next._head.txt +8 -0
- recce/data/checks/__next._index.txt +14 -0
- recce/data/checks/__next._tree.txt +8 -0
- recce/data/checks/__next.checks.__PAGE__.txt +10 -0
- recce/data/checks/__next.checks.txt +4 -0
- recce/data/checks/index.html +2 -0
- recce/data/checks/index.txt +39 -0
- recce/data/index.html +2 -27
- recce/data/index.txt +32 -8
- recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/lineage/__next._full.txt +39 -0
- recce/data/lineage/__next._head.txt +8 -0
- recce/data/lineage/__next._index.txt +14 -0
- recce/data/lineage/__next._tree.txt +8 -0
- recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
- recce/data/lineage/__next.lineage.txt +4 -0
- recce/data/lineage/index.html +2 -0
- recce/data/lineage/index.txt +39 -0
- recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/query/__next._full.txt +37 -0
- recce/data/query/__next._head.txt +8 -0
- recce/data/query/__next._index.txt +14 -0
- recce/data/query/__next._tree.txt +8 -0
- recce/data/query/__next.query.__PAGE__.txt +9 -0
- recce/data/query/__next.query.txt +4 -0
- recce/data/query/index.html +2 -0
- recce/data/query/index.txt +37 -0
- recce/event/CONFIG.bak +1 -0
- recce/event/__init__.py +9 -8
- recce/event/collector.py +6 -2
- recce/event/track.py +10 -0
- recce/github.py +1 -1
- recce/mcp_server.py +725 -0
- recce/models/check.py +433 -15
- recce/models/types.py +61 -2
- recce/pull_request.py +1 -1
- recce/run.py +37 -17
- recce/server.py +216 -21
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +644 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +25 -3
- recce/tasks/dataframe.py +63 -1
- recce/tasks/query.py +40 -3
- recce/tasks/rowcount.py +4 -1
- recce/tasks/schema.py +4 -1
- recce/tasks/utils.py +147 -0
- recce/tasks/valuediff.py +85 -57
- recce/util/api_token.py +11 -2
- recce/util/breaking.py +10 -1
- recce/util/cll.py +1 -2
- recce/util/cloud/__init__.py +15 -0
- recce/util/cloud/base.py +115 -0
- recce/util/cloud/check_events.py +190 -0
- recce/util/cloud/checks.py +242 -0
- recce/util/io.py +2 -2
- recce/util/lineage.py +19 -18
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +254 -5
- recce/util/startup_perf.py +121 -0
- recce/yaml/__init__.py +2 -2
- {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/METADATA +91 -71
- recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
- {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
- recce/data/_next/static/abCX3x3UoIdRLEDWxx4xd/_buildManifest.js +0 -1
- recce/data/_next/static/chunks/181-acc61ddada3bc0ca.js +0 -43
- recce/data/_next/static/chunks/1bff33f1-1ef85cf5e658a751.js +0 -1
- recce/data/_next/static/chunks/217-879a84d70f7a907c.js +0 -2
- recce/data/_next/static/chunks/29e3cc0d-60045b2e47aa3916.js +0 -1
- recce/data/_next/static/chunks/36e1c10d-8e7be4a6c1f6ab2d.js +0 -1
- recce/data/_next/static/chunks/3998a672-03adacad07b346ac.js +0 -1
- recce/data/_next/static/chunks/3a92ee20-1081c360214f9602.js +0 -1
- recce/data/_next/static/chunks/42-cd3c06533f5fd47c.js +0 -9
- recce/data/_next/static/chunks/450c323b-fd94e7ffaa4a5efa.js +0 -1
- recce/data/_next/static/chunks/47d8844f-929aed9b1c73a905.js +0 -1
- recce/data/_next/static/chunks/608-3b079b544e5d5f5e.js +0 -15
- recce/data/_next/static/chunks/6dc81886-adbfa45836061d79.js +0 -1
- recce/data/_next/static/chunks/7a8a3e83-edf6dc64b5d5f0a5.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-d5f0438edd5c2a5b.js +0 -1
- recce/data/_next/static/chunks/86730205-cfb14e3f051bab35.js +0 -1
- recce/data/_next/static/chunks/8d700b6a.8bb140898499c512.js +0 -1
- recce/data/_next/static/chunks/92-607cd1af83c41f43.js +0 -1
- recce/data/_next/static/chunks/9746af58-a42b7d169cacadf0.js +0 -1
- recce/data/_next/static/chunks/a30376cd-de84559016d7e133.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-01ed58b7f971d311.js +0 -1
- recce/data/_next/static/chunks/app/layout-177a410a97e0d018.js +0 -1
- recce/data/_next/static/chunks/app/page-da6e046a8235dbfc.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-4282bdcf459e075c.js +0 -1
- recce/data/_next/static/chunks/bbda5537-9ec25eb1dd62348a.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-08cb668a789d6afd.js +0 -1
- recce/data/_next/static/chunks/ce84277d-2e5d1d46910cf052.js +0 -1
- recce/data/_next/static/chunks/febdd86e-c6b525341634b860.js +0 -54
- recce/data/_next/static/chunks/fee69bc6-2dbccaf9b90474e6.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-app-39061b0166c47f55.js +0 -1
- recce/data/_next/static/chunks/main-b5b3ae20a1405261.js +0 -1
- recce/data/_next/static/chunks/pages/_app-437c455677d62394.js +0 -1
- recce/data/_next/static/chunks/pages/_error-e7650df18ca04bde.js +0 -1
- recce/data/_next/static/chunks/webpack-7b49d5ba7e3a434d.js +0 -1
- recce/data/_next/static/css/17a96168e3a9db13.css +0 -1
- recce/data/_next/static/css/1b121dc4d36aeb4d.css +0 -3
- recce/data/_next/static/css/35c6679a098e1e34.css +0 -1
- recce/data/_next/static/css/951e2e0eea2d4a5b.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/data/_next/static/media/reload-image.79aabb7d.svg +0 -4
- recce/state.py +0 -786
- recce_nightly-1.10.0.20250625.dist-info/RECORD +0 -154
- recce_nightly-1.10.0.20250625.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/conftest.py +0 -17
- tests/adapter/dbt_adapter/dbt_test_helper.py +0 -298
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -25
- tests/adapter/dbt_adapter/test_dbt_cll.py +0 -384
- tests/adapter/dbt_adapter/test_selector.py +0 -202
- tests/tasks/__init__.py +0 -0
- tests/tasks/conftest.py +0 -4
- tests/tasks/test_histogram.py +0 -129
- tests/tasks/test_lineage.py +0 -55
- tests/tasks/test_preset_checks.py +0 -64
- tests/tasks/test_profile.py +0 -397
- tests/tasks/test_query.py +0 -151
- tests/tasks/test_row_count.py +0 -135
- tests/tasks/test_schema.py +0 -122
- tests/tasks/test_top_k.py +0 -77
- tests/tasks/test_valuediff.py +0 -85
- tests/test_cli.py +0 -133
- tests/test_config.py +0 -43
- tests/test_connect_to_cloud.py +0 -82
- tests/test_core.py +0 -29
- tests/test_dbt.py +0 -36
- tests/test_pull_request.py +0 -130
- tests/test_server.py +0 -104
- tests/test_state.py +0 -134
- tests/test_summary.py +0 -65
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- /recce/data/_next/static/media/{montserrat-cyrillic-ext-800-normal.e6e0d8d0.woff → montserrat-cyrillic-ext-800-normal.a4fa76b5.woff} +0 -0
- /recce/data/_next/static/{abCX3x3UoIdRLEDWxx4xd → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
- {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE +0 -0
tests/test_config.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import os.path
|
|
2
|
-
from unittest import TestCase
|
|
3
|
-
from unittest.mock import patch
|
|
4
|
-
|
|
5
|
-
from recce.config import RecceConfig
|
|
6
|
-
from recce.util import SingletonMeta
|
|
7
|
-
|
|
8
|
-
test_root_path = os.path.dirname(os.path.abspath(__file__))
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class RecceConfigTestCase(TestCase):
|
|
12
|
-
def setUp(self):
|
|
13
|
-
self.recce_config_path = os.path.join(test_root_path, "data", "config", "recce.yml")
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
def tearDown(self):
|
|
17
|
-
# Reset the SingletonMeta instances due to RecceConfig is a singleton
|
|
18
|
-
SingletonMeta._instances = {}
|
|
19
|
-
|
|
20
|
-
def test_load_recce_config(self):
|
|
21
|
-
config = RecceConfig(self.recce_config_path)
|
|
22
|
-
|
|
23
|
-
# Test data contains 2 checks
|
|
24
|
-
preset_checks = config.config.get("checks")
|
|
25
|
-
self.assertIsNotNone(preset_checks)
|
|
26
|
-
self.assertIsInstance(preset_checks, list)
|
|
27
|
-
self.assertEqual(len(preset_checks), 2)
|
|
28
|
-
|
|
29
|
-
@patch("recce.config.RecceConfig.save")
|
|
30
|
-
def test_recce_config_not_found(self, mock_save):
|
|
31
|
-
default_config = RecceConfig("NOT_EXISTING_FILE")
|
|
32
|
-
assert mock_save.called is True
|
|
33
|
-
# Default config should be generated
|
|
34
|
-
preset_checks = default_config.config.get("checks")
|
|
35
|
-
self.assertIsNotNone(default_config.config)
|
|
36
|
-
self.assertIsInstance(preset_checks, list)
|
|
37
|
-
self.assertEqual(len(preset_checks), 2)
|
|
38
|
-
|
|
39
|
-
@patch("recce.yaml.safe_load")
|
|
40
|
-
def test_recce_config_null_checks(self, mock_yaml_safe_load):
|
|
41
|
-
# mock to load a yaml file with null checks
|
|
42
|
-
mock_yaml_safe_load.return_value = {"checks": None}
|
|
43
|
-
RecceConfig(self.recce_config_path)
|
tests/test_connect_to_cloud.py
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
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()
|
tests/test_core.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# noinspection PyUnresolvedReferences
|
|
2
|
-
from tests.adapter.dbt_adapter.conftest import dbt_test_helper # noqa: F401
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def test_lineage_diff(dbt_test_helper):
|
|
6
|
-
sql_model1 = """
|
|
7
|
-
select a from T
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
sql_model2 = """
|
|
11
|
-
select a from {{ ref("model1") }}
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
sql_model2_ = """
|
|
15
|
-
select
|
|
16
|
-
a,b
|
|
17
|
-
from
|
|
18
|
-
{{ ref("model1") }}
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
dbt_test_helper.create_model("model1", sql_model1, sql_model1)
|
|
22
|
-
dbt_test_helper.create_model("model2", sql_model2, sql_model2_)
|
|
23
|
-
result = dbt_test_helper.context.get_lineage_diff()
|
|
24
|
-
nodediff = result.diff.get("model1")
|
|
25
|
-
assert nodediff is None
|
|
26
|
-
nodediff2 = result.diff.get("model2")
|
|
27
|
-
assert (
|
|
28
|
-
nodediff2 is not None and nodediff2.change_status == "modified" and nodediff2.change.category == "non_breaking"
|
|
29
|
-
)
|
tests/test_dbt.py
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from unittest import TestCase
|
|
3
|
-
from unittest.mock import MagicMock
|
|
4
|
-
|
|
5
|
-
from recce.adapter.dbt_adapter import DbtAdapter, load_catalog, load_manifest
|
|
6
|
-
|
|
7
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestAdapterLineage(TestCase):
|
|
11
|
-
def setUp(self) -> None:
|
|
12
|
-
self.manifest = load_manifest(path=os.path.join(current_dir, "manifest.json"))
|
|
13
|
-
assert self.manifest is not None
|
|
14
|
-
|
|
15
|
-
self.catalog = load_catalog(path=os.path.join(current_dir, "catalog.json"))
|
|
16
|
-
assert self.catalog is not None
|
|
17
|
-
|
|
18
|
-
def tearDown(self):
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
def test_load_lineage(self):
|
|
22
|
-
dbt_adapter = DbtAdapter(curr_manifest=self.manifest)
|
|
23
|
-
lineage = dbt_adapter.get_lineage()
|
|
24
|
-
assert lineage is not None
|
|
25
|
-
assert lineage["nodes"]["model.jaffle_shop.orders"] is not None
|
|
26
|
-
assert "columns" not in lineage["nodes"]["model.jaffle_shop.orders"]
|
|
27
|
-
|
|
28
|
-
def test_load_lineage_with_catalog(self):
|
|
29
|
-
mock_adapter = MagicMock()
|
|
30
|
-
mock_adapter.type.return_value = None
|
|
31
|
-
|
|
32
|
-
dbt_adapter = DbtAdapter(curr_manifest=self.manifest, curr_catalog=self.catalog)
|
|
33
|
-
dbt_adapter.adapter = mock_adapter
|
|
34
|
-
lineage = dbt_adapter.get_lineage()
|
|
35
|
-
assert lineage is not None
|
|
36
|
-
assert len(lineage["nodes"]["model.jaffle_shop.orders"]["columns"]) == 9
|
tests/test_pull_request.py
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from unittest.mock import patch
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
import requests
|
|
6
|
-
|
|
7
|
-
from recce.pull_request import _fetch_pr_title, fetch_pr_metadata_from_event_path
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def mock_github_event(tmp_path, monkeypatch):
|
|
12
|
-
event_data = {
|
|
13
|
-
"number": 1,
|
|
14
|
-
"pull_request": {
|
|
15
|
-
"_links": {
|
|
16
|
-
"html": {"href": "https://github.com/xyz/abc/pull/1"},
|
|
17
|
-
"self": {"href": "api_url_here"},
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
}
|
|
21
|
-
event_file = tmp_path / "github_event.json"
|
|
22
|
-
with open(event_file, "w") as f:
|
|
23
|
-
json.dump(event_data, f)
|
|
24
|
-
|
|
25
|
-
monkeypatch.setenv("GITHUB_EVENT_PATH", str(event_file))
|
|
26
|
-
monkeypatch.setenv("GITHUB_REPOSITORY", "abc/xyz")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@pytest.fixture
|
|
30
|
-
def mock_github_token(monkeypatch):
|
|
31
|
-
monkeypatch.setenv("GITHUB_TOKEN", "github_token_here")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class MockResponse:
|
|
35
|
-
def __init__(self, status_code, json_data=None):
|
|
36
|
-
self.status_code = status_code
|
|
37
|
-
self.json_data = json_data
|
|
38
|
-
|
|
39
|
-
def json(self):
|
|
40
|
-
return self.json_data
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@pytest.fixture
|
|
44
|
-
def mock_get_request_success(monkeypatch):
|
|
45
|
-
def mock_get(*args, **kwargs):
|
|
46
|
-
return MockResponse(200, json_data={"title": "Update README.md"})
|
|
47
|
-
|
|
48
|
-
monkeypatch.setattr(requests, "get", mock_get)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@pytest.fixture
|
|
52
|
-
def mock_get_request_failure(monkeypatch):
|
|
53
|
-
def mock_get(*args, **kwargs):
|
|
54
|
-
return MockResponse(404)
|
|
55
|
-
|
|
56
|
-
monkeypatch.setattr(requests, "get", mock_get)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_fetch_pr_metadata(mock_github_event, mock_github_token, mock_get_request_success):
|
|
60
|
-
result = fetch_pr_metadata_from_event_path()
|
|
61
|
-
|
|
62
|
-
assert result is not None
|
|
63
|
-
assert result["github_pr_id"] == 1
|
|
64
|
-
assert result["github_pr_url"] == "https://github.com/xyz/abc/pull/1"
|
|
65
|
-
assert result["github_pr_title"] == "Update README.md"
|
|
66
|
-
assert result["github_repository"] == "abc/xyz"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def test_fetch_pr_metadata_no_event(mock_get_request_success, monkeypatch):
|
|
70
|
-
monkeypatch.delenv("GITHUB_EVENT_PATH", raising=False)
|
|
71
|
-
|
|
72
|
-
result = fetch_pr_metadata_from_event_path()
|
|
73
|
-
|
|
74
|
-
assert result is None
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def test_fetch_pr_metadata_request_error(mock_github_event, mock_get_request_failure):
|
|
78
|
-
result = fetch_pr_metadata_from_event_path()
|
|
79
|
-
|
|
80
|
-
assert result is not None
|
|
81
|
-
assert result["github_pr_id"] == 1
|
|
82
|
-
assert result["github_pr_url"] == "https://github.com/xyz/abc/pull/1"
|
|
83
|
-
assert result["github_pr_title"] is None
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def test_fetch_pr_metadata_exception(mock_github_event, monkeypatch):
|
|
87
|
-
monkeypatch.setenv("GITHUB_EVENT_PATH", "nonexistent_path")
|
|
88
|
-
|
|
89
|
-
result = fetch_pr_metadata_from_event_path()
|
|
90
|
-
|
|
91
|
-
assert result is None
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def test_fetch_pr_metadata_no_token(mock_github_event):
|
|
95
|
-
with patch.dict("os.environ", clear=True):
|
|
96
|
-
result = fetch_pr_metadata_from_event_path()
|
|
97
|
-
|
|
98
|
-
assert result is None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def test_fetch_pr_title(mock_github_token, mock_get_request_success):
|
|
102
|
-
result = _fetch_pr_title("api_url_here")
|
|
103
|
-
|
|
104
|
-
assert result == "Update README.md"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_fetch_pr_title_request_error(mock_github_token, mock_get_request_failure):
|
|
108
|
-
result = _fetch_pr_title("api_url_here")
|
|
109
|
-
|
|
110
|
-
assert result is None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def test_fetch_pr_title_exception(mock_github_token, monkeypatch):
|
|
114
|
-
monkeypatch.setenv("GITHUB_TOKEN", "github_token_here")
|
|
115
|
-
|
|
116
|
-
def mock_get(*args, **kwargs):
|
|
117
|
-
raise Exception("Test exception")
|
|
118
|
-
|
|
119
|
-
monkeypatch.setattr(requests, "get", mock_get)
|
|
120
|
-
|
|
121
|
-
result = _fetch_pr_title("api_url_here")
|
|
122
|
-
|
|
123
|
-
assert result is None
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def test_fetch_pr_title_no_token():
|
|
127
|
-
with patch.dict("os.environ", clear=True):
|
|
128
|
-
result = _fetch_pr_title("api_url_here")
|
|
129
|
-
|
|
130
|
-
assert result is None
|
tests/test_server.py
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from fastapi.testclient import TestClient
|
|
5
|
-
|
|
6
|
-
from recce.core import default_context
|
|
7
|
-
from recce.server import app
|
|
8
|
-
|
|
9
|
-
# noinspection PyUnresolvedReferences
|
|
10
|
-
from tests.adapter.dbt_adapter.conftest import dbt_test_helper # noqa: F401
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@pytest.fixture
|
|
14
|
-
def temp_folder():
|
|
15
|
-
import tempfile
|
|
16
|
-
|
|
17
|
-
temp_dir = tempfile.mkdtemp()
|
|
18
|
-
yield temp_dir
|
|
19
|
-
|
|
20
|
-
import shutil
|
|
21
|
-
|
|
22
|
-
shutil.rmtree(temp_dir)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_health():
|
|
26
|
-
client = TestClient(app)
|
|
27
|
-
response = client.get("/api/health")
|
|
28
|
-
assert response.status_code == 200
|
|
29
|
-
assert response.json() == {"status": "ok"}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def test_stateless(dbt_test_helper):
|
|
33
|
-
context = default_context()
|
|
34
|
-
from recce.state import RecceStateLoader
|
|
35
|
-
|
|
36
|
-
context.state_loader = RecceStateLoader()
|
|
37
|
-
client = TestClient(app)
|
|
38
|
-
response = client.get("/api/info")
|
|
39
|
-
assert response.status_code == 200
|
|
40
|
-
info = response.json()
|
|
41
|
-
assert info["file_mode"] is False
|
|
42
|
-
assert info["cloud_mode"] is False
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def test_file_mode(dbt_test_helper):
|
|
46
|
-
context = default_context()
|
|
47
|
-
from recce.state import RecceStateLoader
|
|
48
|
-
|
|
49
|
-
context.state_loader = RecceStateLoader(state_file="/tmp/recce_state.json")
|
|
50
|
-
client = TestClient(app)
|
|
51
|
-
response = client.get("/api/info")
|
|
52
|
-
assert response.status_code == 200
|
|
53
|
-
info = response.json()
|
|
54
|
-
assert info["file_mode"] is True
|
|
55
|
-
assert info["filename"] == "recce_state.json"
|
|
56
|
-
assert info["cloud_mode"] is False
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_saveas_and_rename(dbt_test_helper, temp_folder):
|
|
60
|
-
context = default_context()
|
|
61
|
-
state_file = os.path.join(temp_folder, "recce_state.json")
|
|
62
|
-
state_file2 = os.path.join(temp_folder, "recce_state2.json")
|
|
63
|
-
state_file3 = os.path.join(temp_folder, "recce_state3.json")
|
|
64
|
-
os.makedirs(os.path.join(temp_folder, "dir.json"))
|
|
65
|
-
|
|
66
|
-
from recce.state import RecceStateLoader
|
|
67
|
-
|
|
68
|
-
context.state_loader = RecceStateLoader(state_file=state_file)
|
|
69
|
-
client = TestClient(app)
|
|
70
|
-
|
|
71
|
-
response = client.post("/api/save", json={"filename": "recce_state2.json"})
|
|
72
|
-
assert response.status_code == 200
|
|
73
|
-
assert os.path.exists(state_file)
|
|
74
|
-
|
|
75
|
-
response = client.post("/api/save-as", json={"filename": "recce_state2.json"})
|
|
76
|
-
assert response.status_code == 200
|
|
77
|
-
assert os.path.exists(state_file2)
|
|
78
|
-
assert context.state_loader.state_file == os.path.join(temp_folder, "recce_state2.json")
|
|
79
|
-
|
|
80
|
-
# Same file
|
|
81
|
-
response = client.post("/api/save-as", json={"filename": "recce_state2.json"})
|
|
82
|
-
assert response.status_code == 400
|
|
83
|
-
|
|
84
|
-
# folder
|
|
85
|
-
response = client.post("/api/save-as", json={"filename": "dir.json"})
|
|
86
|
-
assert response.status_code == 400
|
|
87
|
-
|
|
88
|
-
# Rename
|
|
89
|
-
response = client.post("/api/rename", json={"filename": "recce_state3.json"})
|
|
90
|
-
assert response.status_code == 200
|
|
91
|
-
assert not os.path.exists(state_file2)
|
|
92
|
-
assert os.path.exists(state_file3)
|
|
93
|
-
assert context.state_loader.state_file == os.path.join(temp_folder, "recce_state3.json")
|
|
94
|
-
|
|
95
|
-
# Conflict
|
|
96
|
-
response = client.post("/api/save-as", json={"filename": "recce_state.json"})
|
|
97
|
-
assert response.status_code == 409
|
|
98
|
-
response = client.post("/api/rename", json={"filename": "recce_state.json"})
|
|
99
|
-
assert response.status_code == 409
|
|
100
|
-
|
|
101
|
-
# Overwrite
|
|
102
|
-
response = client.post("/api/save-as", json={"filename": "recce_state.json", "overwrite": True})
|
|
103
|
-
assert response.status_code == 200
|
|
104
|
-
assert context.state_loader.state_file == os.path.join(temp_folder, "recce_state.json")
|
tests/test_state.py
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# add the RecceState test case
|
|
2
|
-
import os
|
|
3
|
-
import unittest
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
|
|
6
|
-
from recce.core import RecceContext
|
|
7
|
-
from recce.models import Check, Run
|
|
8
|
-
from recce.state import ArtifactsRoot, RecceState, RecceStateLoader
|
|
9
|
-
|
|
10
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TestRecceState(unittest.TestCase):
|
|
14
|
-
def test_merge_checks(self):
|
|
15
|
-
check1 = Check(name="test1", description="", type="query")
|
|
16
|
-
check2 = Check(name="test2", description="", type="query", updated_at=datetime(2000, 1, 1))
|
|
17
|
-
check2_2 = Check(
|
|
18
|
-
name="test2_2", description="", type="query", updated_at=datetime(2020, 1, 1), check_id=check2.check_id
|
|
19
|
-
)
|
|
20
|
-
check3 = Check(name="test3", description="", type="query")
|
|
21
|
-
|
|
22
|
-
context = RecceContext()
|
|
23
|
-
state = RecceState(checks=[check1], runs=[])
|
|
24
|
-
context.import_state(state)
|
|
25
|
-
self.assertEqual(1, len(context.checks))
|
|
26
|
-
self.assertEqual(check1.name, context.checks[0].name)
|
|
27
|
-
|
|
28
|
-
context = RecceContext(checks=[check1, check2])
|
|
29
|
-
state = RecceState(checks=[check1, check2_2, check3], runs=[])
|
|
30
|
-
context.import_state(state)
|
|
31
|
-
self.assertEqual(3, len(context.checks))
|
|
32
|
-
self.assertEqual(check2_2.name, context.checks[1].name)
|
|
33
|
-
|
|
34
|
-
def test_merge_preset_checks(self):
|
|
35
|
-
check1 = Check(
|
|
36
|
-
name="test1",
|
|
37
|
-
description="test1",
|
|
38
|
-
type="query",
|
|
39
|
-
params=dict(foo="bar"),
|
|
40
|
-
updated_at=datetime(2000, 1, 1),
|
|
41
|
-
is_preset=True,
|
|
42
|
-
)
|
|
43
|
-
check2 = Check(
|
|
44
|
-
name="test2",
|
|
45
|
-
description="test2",
|
|
46
|
-
type="query",
|
|
47
|
-
params=dict(foo="bar"),
|
|
48
|
-
updated_at=datetime(2001, 1, 1),
|
|
49
|
-
is_preset=True,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
context = RecceContext(checks=[check1])
|
|
53
|
-
state = RecceState(checks=[check2], runs=[])
|
|
54
|
-
context.import_state(state)
|
|
55
|
-
self.assertEqual(1, len(context.checks))
|
|
56
|
-
self.assertEqual(check2.name, context.checks[0].name)
|
|
57
|
-
|
|
58
|
-
context = RecceContext(checks=[check2])
|
|
59
|
-
state = RecceState(checks=[check1], runs=[])
|
|
60
|
-
context.import_state(state)
|
|
61
|
-
self.assertEqual(1, len(context.checks))
|
|
62
|
-
self.assertEqual(check2.name, context.checks[0].name)
|
|
63
|
-
|
|
64
|
-
def test_revert_checks(self):
|
|
65
|
-
check1 = Check(name="test1", description="", type="query")
|
|
66
|
-
check2 = Check(name="test2", description="", type="query")
|
|
67
|
-
check2_2 = Check(name="test2_2", description="", type="query", check_id=check2.check_id)
|
|
68
|
-
check3 = Check(name="test3", description="", type="query")
|
|
69
|
-
|
|
70
|
-
context = RecceContext(checks=[check1, check2])
|
|
71
|
-
state = RecceState(checks=[check2_2, check3], runs=[])
|
|
72
|
-
context.import_state(state, merge=False)
|
|
73
|
-
self.assertEqual(2, len(context.checks))
|
|
74
|
-
self.assertEqual(check2_2.name, context.checks[0].name)
|
|
75
|
-
|
|
76
|
-
def test_merge_runs(self):
|
|
77
|
-
run1 = Run(type="query")
|
|
78
|
-
run2 = Run(type="query")
|
|
79
|
-
run3 = Run(type="query")
|
|
80
|
-
|
|
81
|
-
context = RecceContext(runs=[])
|
|
82
|
-
state = RecceState(runs=[run1])
|
|
83
|
-
context.import_state(state)
|
|
84
|
-
self.assertEqual(1, len(context.runs))
|
|
85
|
-
|
|
86
|
-
context = RecceContext(runs=[run1, run2])
|
|
87
|
-
state = RecceState(runs=[run2, run3])
|
|
88
|
-
context.import_state(state)
|
|
89
|
-
self.assertEqual(3, len(context.runs))
|
|
90
|
-
|
|
91
|
-
def test_merge_dbt_artifacts(self):
|
|
92
|
-
import json
|
|
93
|
-
import os
|
|
94
|
-
|
|
95
|
-
with open(os.path.join(current_dir, "manifest.json"), "r") as f:
|
|
96
|
-
manifest = json.load(f)
|
|
97
|
-
manifest["metadata"]["generated_at"] = "2000-01-01T00:00:00Z"
|
|
98
|
-
artifacts = ArtifactsRoot(
|
|
99
|
-
base=dict(
|
|
100
|
-
manifest=manifest,
|
|
101
|
-
),
|
|
102
|
-
current=dict(
|
|
103
|
-
manifest=manifest,
|
|
104
|
-
),
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
from tests.adapter.dbt_adapter.dbt_test_helper import DbtTestHelper
|
|
108
|
-
|
|
109
|
-
adapter = DbtTestHelper().adapter
|
|
110
|
-
adapter.import_artifacts(artifacts)
|
|
111
|
-
self.assertNotEqual(adapter.base_manifest.metadata.invocation_id, manifest.get("metadata").get("invocation_id"))
|
|
112
|
-
|
|
113
|
-
manifest["metadata"]["generated_at"] = "2099-01-01T00:00:00Z"
|
|
114
|
-
adapter.import_artifacts(artifacts)
|
|
115
|
-
self.assertEqual(adapter.base_manifest.metadata.invocation_id, manifest.get("metadata").get("invocation_id"))
|
|
116
|
-
|
|
117
|
-
def test_state_loader(self):
|
|
118
|
-
# copy ./recce_state.json to temp and open
|
|
119
|
-
|
|
120
|
-
# use library to create a temp file in the context
|
|
121
|
-
import os
|
|
122
|
-
import shutil
|
|
123
|
-
import tempfile
|
|
124
|
-
|
|
125
|
-
with tempfile.NamedTemporaryFile() as f:
|
|
126
|
-
# copy ./recce_state.json to temp file
|
|
127
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
128
|
-
state_file = os.path.join(current_dir, "recce_state.json")
|
|
129
|
-
shutil.copy(state_file, f.name)
|
|
130
|
-
|
|
131
|
-
# load the state file
|
|
132
|
-
state_loader = RecceStateLoader(state_file=f.name)
|
|
133
|
-
state = state_loader.load()
|
|
134
|
-
assert len(state.runs) == 17
|
tests/test_summary.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import unittest
|
|
3
|
-
|
|
4
|
-
from recce.adapter.dbt_adapter import DbtAdapter, DbtVersion, load_manifest
|
|
5
|
-
from recce.core import RecceContext, set_default_context
|
|
6
|
-
from recce.summary import (
|
|
7
|
-
_build_lineage_graph,
|
|
8
|
-
generate_mermaid_lineage_graph,
|
|
9
|
-
generate_summary_metadata,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
13
|
-
base_manifest_dir = os.path.join(current_dir, "data", "manifest", "base")
|
|
14
|
-
pr2_manifest_dir = os.path.join(current_dir, "data", "manifest", "pr2") # Pull Request 2l
|
|
15
|
-
|
|
16
|
-
dbt_version = DbtVersion()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@unittest.skipIf(dbt_version < "1.8.1", "Dbt version is less than 1.8.1")
|
|
20
|
-
def test_generate_summary_metadata():
|
|
21
|
-
manifest = load_manifest(path=os.path.join(current_dir, "manifest.json"))
|
|
22
|
-
assert manifest is not None
|
|
23
|
-
dbt_adapter = DbtAdapter(curr_manifest=manifest)
|
|
24
|
-
curr_lineage = dbt_adapter.get_lineage()
|
|
25
|
-
base_lineage = dbt_adapter.get_lineage()
|
|
26
|
-
|
|
27
|
-
# Summary with no changes
|
|
28
|
-
generate_summary_metadata(curr_lineage, base_lineage)
|
|
29
|
-
|
|
30
|
-
base_manifest = load_manifest(path=os.path.join(base_manifest_dir, "manifest.json"))
|
|
31
|
-
curr_manifest = load_manifest(path=os.path.join(pr2_manifest_dir, "manifest.json"))
|
|
32
|
-
dbt_adapter = DbtAdapter(curr_manifest=curr_manifest, base_manifest=base_manifest)
|
|
33
|
-
curr_lineage = dbt_adapter.get_lineage()
|
|
34
|
-
base_lineage = dbt_adapter.get_lineage(base=True)
|
|
35
|
-
generate_summary_metadata(curr_lineage, base_lineage)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@unittest.skipIf(dbt_version < "v1.8.1", "Dbt version is less than 1.8.1")
|
|
39
|
-
def test_build_lineage_graph():
|
|
40
|
-
base_manifest = load_manifest(path=os.path.join(base_manifest_dir, "manifest.json"))
|
|
41
|
-
curr_manifest = load_manifest(path=os.path.join(pr2_manifest_dir, "manifest.json"))
|
|
42
|
-
dbt_adapter = DbtAdapter(curr_manifest=curr_manifest, base_manifest=base_manifest)
|
|
43
|
-
curr_lineage = dbt_adapter.get_lineage()
|
|
44
|
-
base_lineage = dbt_adapter.get_lineage(base=True)
|
|
45
|
-
|
|
46
|
-
lineage_graph = _build_lineage_graph(curr_lineage, base_lineage)
|
|
47
|
-
# Modified nodes: 3
|
|
48
|
-
# - int_customer_orders: added
|
|
49
|
-
# - int_customer_payments: added
|
|
50
|
-
# - customers: modified
|
|
51
|
-
assert len(lineage_graph.modified_set) == 3
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@unittest.skipIf(dbt_version < "v1.8.1", "Dbt version is less than 1.8.1")
|
|
55
|
-
def test_generate_mermaid_lineage_graph():
|
|
56
|
-
set_default_context(RecceContext())
|
|
57
|
-
base_manifest = load_manifest(path=os.path.join(base_manifest_dir, "manifest.json"))
|
|
58
|
-
curr_manifest = load_manifest(path=os.path.join(pr2_manifest_dir, "manifest.json"))
|
|
59
|
-
dbt_adapter = DbtAdapter(curr_manifest=curr_manifest, base_manifest=base_manifest)
|
|
60
|
-
curr_lineage = dbt_adapter.get_lineage()
|
|
61
|
-
base_lineage = dbt_adapter.get_lineage(base=True)
|
|
62
|
-
graph = _build_lineage_graph(curr_lineage, base_lineage)
|
|
63
|
-
mermaid_content, is_empty_graph, is_partial_graph = generate_mermaid_lineage_graph(graph)
|
|
64
|
-
assert is_empty_graph is False
|
|
65
|
-
assert is_partial_graph is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{recce_nightly-1.10.0.20250625.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|