recce-nightly 0.57.0.20250309__py3-none-any.whl → 0.57.0.20250311__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 +6 -5
- recce/data/404.html +1 -1
- recce/data/_next/static/chunks/app/page-fad1215077528dbd.js +1 -0
- recce/data/index.html +2 -2
- recce/data/index.txt +2 -2
- {recce_nightly-0.57.0.20250309.dist-info → recce_nightly-0.57.0.20250311.dist-info}/METADATA +1 -1
- {recce_nightly-0.57.0.20250309.dist-info → recce_nightly-0.57.0.20250311.dist-info}/RECORD +17 -16
- tests/adapter/dbt_adapter/dbt_test_helper.py +140 -73
- tests/adapter/dbt_adapter/test_dbt_cll.py +100 -0
- tests/test_dbt.py +0 -175
- recce/data/_next/static/chunks/app/page-6ff79de000e5b4bd.js +0 -1
- /recce/data/_next/static/{-QjeAspYdqVIWaYcbRmtf → K5VaOY9xgYHtASZ6IDWLR}/_buildManifest.js +0 -0
- /recce/data/_next/static/{-QjeAspYdqVIWaYcbRmtf → K5VaOY9xgYHtASZ6IDWLR}/_ssgManifest.js +0 -0
- {recce_nightly-0.57.0.20250309.dist-info → recce_nightly-0.57.0.20250311.dist-info}/LICENSE +0 -0
- {recce_nightly-0.57.0.20250309.dist-info → recce_nightly-0.57.0.20250311.dist-info}/WHEEL +0 -0
- {recce_nightly-0.57.0.20250309.dist-info → recce_nightly-0.57.0.20250311.dist-info}/entry_points.txt +0 -0
- {recce_nightly-0.57.0.20250309.dist-info → recce_nightly-0.57.0.20250311.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from recce.adapter.dbt_adapter import DbtAdapter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_cll_basic(dbt_test_helper):
|
|
5
|
+
dbt_test_helper.create_model("model1", curr_sql="select 1 as c", curr_columns={"c": "int"})
|
|
6
|
+
dbt_test_helper.create_model("model2", curr_sql='select c from {{ ref("model1") }}', curr_columns={"c": "int"})
|
|
7
|
+
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
8
|
+
result = adapter.get_lineage()
|
|
9
|
+
assert result['nodes']['model2']['columns']['c']['depends_on'][0].column == 'c'
|
|
10
|
+
assert result['nodes']['model2']['columns']['c']['depends_on'][0].node == 'model1'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_cll_table_alisa(dbt_test_helper):
|
|
14
|
+
def patch_node(node):
|
|
15
|
+
node['alias'] = 'model1_alias'
|
|
16
|
+
|
|
17
|
+
dbt_test_helper.create_model("model1", curr_sql="select 1 as c", curr_columns={"c": "int"}, patch_func=patch_node)
|
|
18
|
+
dbt_test_helper.create_model("model2", curr_sql='select c from {{ ref("model1") }}', curr_columns={"c": "int"})
|
|
19
|
+
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
20
|
+
result = adapter.get_lineage()
|
|
21
|
+
assert result['nodes']['model2']['columns']['c']['depends_on'][0].column == 'c'
|
|
22
|
+
assert result['nodes']['model2']['columns']['c']['depends_on'][0].node == 'model1'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_seed(dbt_test_helper):
|
|
26
|
+
csv_data_curr = """
|
|
27
|
+
customer_id,name,age
|
|
28
|
+
1,Alice,30
|
|
29
|
+
2,Bob,25
|
|
30
|
+
3,Charlie,35
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
dbt_test_helper.create_model("seed1",
|
|
34
|
+
curr_csv=csv_data_curr,
|
|
35
|
+
curr_columns={"customer_id": "varchar", "name": "varchar", "age": "int"},
|
|
36
|
+
resource_type="seed")
|
|
37
|
+
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
38
|
+
result = adapter.get_lineage()
|
|
39
|
+
|
|
40
|
+
assert result['nodes']['seed1']['columns']['customer_id']['transformation_type'] == 'source'
|
|
41
|
+
assert len(result['nodes']['seed1']['columns']['customer_id']['depends_on']) == 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_python_model(dbt_test_helper):
|
|
45
|
+
def python_node(node):
|
|
46
|
+
node['language'] = 'python'
|
|
47
|
+
|
|
48
|
+
csv_data_curr = """
|
|
49
|
+
customer_id,name,age
|
|
50
|
+
1,Alice,30
|
|
51
|
+
2,Bob,25
|
|
52
|
+
3,Charlie,35
|
|
53
|
+
"""
|
|
54
|
+
dbt_test_helper.create_model("model1",
|
|
55
|
+
curr_csv=csv_data_curr,
|
|
56
|
+
curr_columns={"customer_id": "varchar", "name": "varchar", "age": "int"})
|
|
57
|
+
dbt_test_helper.create_model("model2",
|
|
58
|
+
curr_csv=csv_data_curr,
|
|
59
|
+
curr_columns={"customer_id": "varchar", "name": "varchar", "age": "int"},
|
|
60
|
+
depends_on=["model1"],
|
|
61
|
+
patch_func=python_node)
|
|
62
|
+
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
63
|
+
assert not adapter.is_python_model('model1')
|
|
64
|
+
assert adapter.is_python_model('model2')
|
|
65
|
+
|
|
66
|
+
result = adapter.get_lineage()
|
|
67
|
+
assert result['nodes']['model2']['columns']['customer_id']['transformation_type'] == 'unknown'
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_source(dbt_test_helper):
|
|
71
|
+
csv_data_curr = """
|
|
72
|
+
customer_id,name,age
|
|
73
|
+
1,Alice,30
|
|
74
|
+
2,Bob,25
|
|
75
|
+
3,Charlie,35
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
dbt_test_helper.create_source(
|
|
79
|
+
"source1",
|
|
80
|
+
"table1",
|
|
81
|
+
curr_csv=csv_data_curr,
|
|
82
|
+
curr_columns={"customer_id": "varchar", "name": "varchar", "age": "int"})
|
|
83
|
+
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
84
|
+
result = adapter.get_lineage()
|
|
85
|
+
assert result['nodes']['source1.table1']['columns']['customer_id']['transformation_type'] == 'source'
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_parse_error(dbt_test_helper):
|
|
89
|
+
dbt_test_helper.create_model("model1", curr_sql="select 1 as c", curr_columns={"c": "int"})
|
|
90
|
+
dbt_test_helper.create_model("model2", curr_sql='this is not a valid sql', curr_columns={"c": "int"})
|
|
91
|
+
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
92
|
+
result = adapter.get_lineage()
|
|
93
|
+
assert result['nodes']['model2']['columns']['c']['transformation_type'] == 'unknown'
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_model_without_catalog(dbt_test_helper):
|
|
97
|
+
dbt_test_helper.create_model("model1", curr_sql="select 1 as c")
|
|
98
|
+
adapter: DbtAdapter = dbt_test_helper.context.adapter
|
|
99
|
+
result = adapter.get_lineage()
|
|
100
|
+
assert not hasattr(result['nodes']['model1'], 'columns')
|
tests/test_dbt.py
CHANGED
|
@@ -3,8 +3,6 @@ from unittest import TestCase
|
|
|
3
3
|
from unittest.mock import patch, MagicMock
|
|
4
4
|
|
|
5
5
|
from recce.adapter.dbt_adapter import load_manifest, load_catalog, DbtAdapter
|
|
6
|
-
from recce.exceptions import RecceException
|
|
7
|
-
from recce.util.cll import ColumnLevelDependencyColumn, ColumnLevelDependsOn
|
|
8
6
|
|
|
9
7
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
10
8
|
|
|
@@ -40,176 +38,3 @@ class TestAdapterLineage(TestCase):
|
|
|
40
38
|
lineage = dbt_adapter.get_lineage()
|
|
41
39
|
assert lineage is not None
|
|
42
40
|
assert len(lineage['nodes']['model.jaffle_shop.orders']['columns']) == 9
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class TestAdapterColumnLineage(TestCase):
|
|
46
|
-
|
|
47
|
-
def setUp(self) -> None:
|
|
48
|
-
manifest = load_manifest(path=os.path.join(current_dir, 'manifest.json'))
|
|
49
|
-
self.dbt_adapter = DbtAdapter(curr_manifest=manifest)
|
|
50
|
-
self.patcher_generate_sql = patch.object(self.dbt_adapter, 'generate_sql')
|
|
51
|
-
self.mock_generate_sql = self.patcher_generate_sql.start()
|
|
52
|
-
|
|
53
|
-
self.mock_adapter = MagicMock()
|
|
54
|
-
self.dbt_adapter.adapter = self.mock_adapter
|
|
55
|
-
self.mock_adapter.type.return_value = None
|
|
56
|
-
|
|
57
|
-
# mock 'is_python_model' per test case, default to False
|
|
58
|
-
self.patcher_is_python_model = patch.object(self.dbt_adapter, 'is_python_model', return_value=False)
|
|
59
|
-
self.mock_is_python_model = self.patcher_is_python_model.start()
|
|
60
|
-
|
|
61
|
-
# mock return value per test case
|
|
62
|
-
self.patcher_cll = patch('recce.adapter.dbt_adapter.cll')
|
|
63
|
-
self.mock_cll = self.patcher_cll.start()
|
|
64
|
-
|
|
65
|
-
def tearDown(self):
|
|
66
|
-
self.patcher_generate_sql.stop()
|
|
67
|
-
self.patcher_is_python_model.stop()
|
|
68
|
-
self.patcher_cll.stop()
|
|
69
|
-
|
|
70
|
-
def test_is_python_model(self):
|
|
71
|
-
self.assertFalse(self.dbt_adapter.is_python_model('model.jaffle_shop.orders'))
|
|
72
|
-
|
|
73
|
-
def test_seed(self):
|
|
74
|
-
nodes = {
|
|
75
|
-
'seed1': {
|
|
76
|
-
'id': 'seed1',
|
|
77
|
-
'name': 'seed1',
|
|
78
|
-
'resource_type': 'seed',
|
|
79
|
-
'raw_code': None,
|
|
80
|
-
'columns': {
|
|
81
|
-
'a': {
|
|
82
|
-
'name': 'a',
|
|
83
|
-
'type': 'int'
|
|
84
|
-
},
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
parents_map = {
|
|
89
|
-
'seed1': []
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
self.dbt_adapter.append_column_lineage(nodes, parents_map)
|
|
93
|
-
self.assertEqual(nodes['seed1']['columns']['a']['transformation_type'], 'source')
|
|
94
|
-
self.assertEqual(nodes['seed1']['columns']['a']['depends_on'], [])
|
|
95
|
-
|
|
96
|
-
def test_source(self):
|
|
97
|
-
nodes = {
|
|
98
|
-
'source1': {
|
|
99
|
-
'id': 'source1',
|
|
100
|
-
'name': 'source1',
|
|
101
|
-
'resource_type': 'source',
|
|
102
|
-
'raw_code': None,
|
|
103
|
-
'columns': {
|
|
104
|
-
'a': {
|
|
105
|
-
'name': 'a',
|
|
106
|
-
'type': 'int'
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
parents_map = {
|
|
112
|
-
'source1': []
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
self.dbt_adapter.append_column_lineage(nodes, parents_map)
|
|
116
|
-
self.assertEqual(nodes['source1']['columns']['a']['transformation_type'], 'source')
|
|
117
|
-
self.assertEqual(nodes['source1']['columns']['a']['depends_on'], [])
|
|
118
|
-
|
|
119
|
-
def test_python_model(self):
|
|
120
|
-
nodes = {
|
|
121
|
-
'py_model': {
|
|
122
|
-
'id': 'py_model',
|
|
123
|
-
'name': 'py_model',
|
|
124
|
-
'resource_type': 'model',
|
|
125
|
-
'raw_code': """
|
|
126
|
-
def model(dbt, session):
|
|
127
|
-
dbt.config(materialized = "table")
|
|
128
|
-
df = dbt.ref("customers")
|
|
129
|
-
return df
|
|
130
|
-
""",
|
|
131
|
-
'columns': {
|
|
132
|
-
'a': {
|
|
133
|
-
'name': 'a',
|
|
134
|
-
'type': 'int'
|
|
135
|
-
},
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
parents_map = {
|
|
140
|
-
'py_model': ['model.jaffle_shop.customers']
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
self.mock_is_python_model.return_value = True
|
|
144
|
-
self.dbt_adapter.append_column_lineage(nodes, parents_map)
|
|
145
|
-
self.assertEqual(nodes['py_model']['columns']['a']['transformation_type'], 'unknown')
|
|
146
|
-
self.assertEqual(nodes['py_model']['columns']['a']['depends_on'], [])
|
|
147
|
-
|
|
148
|
-
def test_model(self):
|
|
149
|
-
nodes = {
|
|
150
|
-
'model1': {
|
|
151
|
-
'id': 'model1',
|
|
152
|
-
'name': 'model1',
|
|
153
|
-
'resource_type': 'model',
|
|
154
|
-
'raw_code': 'select * from model2',
|
|
155
|
-
'columns': {
|
|
156
|
-
'a': {
|
|
157
|
-
'name': 'a',
|
|
158
|
-
'type': 'int'
|
|
159
|
-
},
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
parents_map = {
|
|
164
|
-
'model1': ['model2']
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
self.mock_cll.return_value = {'a': ColumnLevelDependencyColumn(
|
|
168
|
-
type='passthrough',
|
|
169
|
-
depends_on=[ColumnLevelDependsOn(node='model2', column='a')]
|
|
170
|
-
)}
|
|
171
|
-
self.dbt_adapter.append_column_lineage(nodes, parents_map)
|
|
172
|
-
self.assertEqual(nodes['model1']['columns']['a']['transformation_type'], 'passthrough')
|
|
173
|
-
self.assertEqual(nodes['model1']['columns']['a']['depends_on'][0].node, 'model2')
|
|
174
|
-
self.assertEqual(nodes['model1']['columns']['a']['depends_on'][0].column, 'a')
|
|
175
|
-
|
|
176
|
-
def test_model_parse_error(self):
|
|
177
|
-
nodes = {
|
|
178
|
-
'model1': {
|
|
179
|
-
'id': 'model1',
|
|
180
|
-
'name': 'model1',
|
|
181
|
-
'resource_type': 'model',
|
|
182
|
-
'raw_code': 'select * from model2',
|
|
183
|
-
'columns': {
|
|
184
|
-
'a': {
|
|
185
|
-
'name': 'a',
|
|
186
|
-
'type': 'int'
|
|
187
|
-
},
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
parents_map = {
|
|
192
|
-
'model1': ['model2']
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
self.mock_cll.side_effect = RecceException('Failed to parse SQL')
|
|
196
|
-
self.dbt_adapter.append_column_lineage(nodes, parents_map)
|
|
197
|
-
self.assertEqual(nodes['model1']['columns']['a']['transformation_type'], 'unknown')
|
|
198
|
-
self.assertEqual(nodes['model1']['columns']['a']['depends_on'], [])
|
|
199
|
-
|
|
200
|
-
def test_model_without_catalog(self):
|
|
201
|
-
# no 'columns' key in node
|
|
202
|
-
nodes = {
|
|
203
|
-
'model1': {
|
|
204
|
-
'id': 'model1',
|
|
205
|
-
'name': 'model1',
|
|
206
|
-
'resource_type': 'model',
|
|
207
|
-
'raw_code': 'select * from model2',
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
parents_map = {
|
|
211
|
-
'model1': ['model2']
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
self.dbt_adapter.append_column_lineage(nodes, parents_map)
|
|
215
|
-
self.assertIsNone(nodes['model1'].get('columns'))
|