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.

@@ -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'))