Skip to content

Commit 9ae5cbc

Browse files
fix: resolve rebase conflicts and fix test assertions for FullyQualifiedEntityName wrapping
1 parent 6d190f0 commit 9ae5cbc

2 files changed

Lines changed: 53 additions & 132 deletions

File tree

ingestion/src/metadata/ingestion/source/dashboard/quicksight/metadata.py

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def _build_column_lineage_from_parser(
243243
lineage_parser: LineageParser,
244244
from_entity: Table,
245245
data_model_entity: DashboardDataModel,
246-
) -> List[ColumnLineage]:
246+
) -> list[ColumnLineage]:
247247
"""
248248
Build column-level lineage using SQL parser alias mappings.
249249
@@ -263,7 +263,7 @@ def _build_column_lineage_from_parser(
263263
264264
Issue #26670.
265265
"""
266-
column_lineage: List[ColumnLineage] = []
266+
column_lineage: list[ColumnLineage] = []
267267

268268
for col_pair in lineage_parser.column_lineage or []:
269269
# Guard: parser may return single-element tuples in edge cases
@@ -277,32 +277,19 @@ def _build_column_lineage_from_parser(
277277
if parent is not None:
278278
# _parent may be a single Table or an iterable of Tables.
279279
# Normalise to a list so we handle both cases uniformly.
280-
parents = (
281-
list(parent)
282-
if hasattr(parent, "__iter__")
283-
and not isinstance(parent, str)
284-
else [parent]
285-
)
280+
parents = list(parent) if hasattr(parent, "__iter__") and not isinstance(parent, str) else [parent]
286281
entity_table = from_entity.name.root.lower()
287282
# Accept the pair only when at least one parent table
288283
# matches the current upstream entity being processed.
289-
if not any(
290-
str(p)
291-
.replace("<default>.", "")
292-
.split(".")[-1]
293-
.lower()
294-
== entity_table
295-
for p in parents
296-
):
284+
if not any(str(p).replace("<default>.", "").split(".")[-1].lower() == entity_table for p in parents):
297285
continue
298286
else:
299287
# _parent is None — parser could not determine source
300288
# table. Log for visibility but allow the pair through;
301289
# get_column_fqn provides a secondary guard (column must
302290
# exist in entity).
303291
logger.debug(
304-
"No parent table info for column %s; "
305-
"skipping parent-table filter",
292+
"No parent table info for column %s; skipping parent-table filter",
306293
src_col.raw_name,
307294
)
308295

@@ -312,9 +299,7 @@ def _build_column_lineage_from_parser(
312299
tgt_col_name = tgt_col.raw_name.split(".")[-1]
313300

314301
try:
315-
from_col_fqn = get_column_fqn(
316-
table_entity=from_entity, column=src_col_name
317-
)
302+
from_col_fqn = get_column_fqn(table_entity=from_entity, column=src_col_name)
318303
to_col_fqn = self._get_data_model_column_fqn(
319304
data_model_entity=data_model_entity,
320305
column=tgt_col_name,
@@ -327,10 +312,7 @@ def _build_column_lineage_from_parser(
327312
)
328313
)
329314
except Exception as exc: # pylint: disable=broad-except
330-
logger.debug(
331-
f"Failed to build column lineage "
332-
f"for {src_col_name} -> {tgt_col_name}: {exc}"
333-
)
315+
logger.debug(f"Failed to build column lineage for {src_col_name} -> {tgt_col_name}: {exc}")
334316
logger.debug(traceback.format_exc())
335317

336318
# Only fall back to name-based matching when the parser found
@@ -340,9 +322,7 @@ def _build_column_lineage_from_parser(
340322
# manufacturing incorrect cross-table lineage.
341323
if not column_lineage and not lineage_parser.column_lineage:
342324
columns = [col.name.root for col in data_model_entity.columns]
343-
return self._get_column_lineage(
344-
from_entity, data_model_entity, columns
345-
)
325+
return self._get_column_lineage(from_entity, data_model_entity, columns)
346326

347327
return column_lineage
348328

ingestion/tests/unit/topology/dashboard/test_quicksight.py

Lines changed: 45 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -399,28 +399,24 @@ def test_build_column_lineage_from_parser_resolves_alias(self):
399399
mock_from_entity.name.root = "relation_table"
400400
mock_data_model = MagicMock()
401401

402-
with patch(
403-
"metadata.ingestion.source.dashboard.quicksight.metadata.get_column_fqn",
404-
return_value=src_fqn,
405-
) as mock_get_col_fqn:
406-
with patch.object(
402+
with (
403+
patch(
404+
"metadata.ingestion.source.dashboard.quicksight.metadata.get_column_fqn",
405+
return_value=src_fqn,
406+
) as mock_get_col_fqn,
407+
patch.object(
407408
self.quicksight,
408409
"_get_data_model_column_fqn",
409410
return_value=alias_fqn,
410-
) as mock_get_dm_col_fqn:
411-
result = self.quicksight._build_column_lineage_from_parser(
412-
mock_parser, mock_from_entity, mock_data_model
413-
)
411+
) as mock_get_dm_col_fqn,
412+
):
413+
result = self.quicksight._build_column_lineage_from_parser(mock_parser, mock_from_entity, mock_data_model)
414414

415-
mock_get_col_fqn.assert_called_once_with(
416-
table_entity=mock_from_entity, column="id"
417-
)
418-
mock_get_dm_col_fqn.assert_called_once_with(
419-
data_model_entity=mock_data_model, column="relation_id"
420-
)
415+
mock_get_col_fqn.assert_called_once_with(table_entity=mock_from_entity, column="id")
416+
mock_get_dm_col_fqn.assert_called_once_with(data_model_entity=mock_data_model, column="relation_id")
421417
assert len(result) == 1
422-
assert result[0].fromColumns == [src_fqn]
423-
assert result[0].toColumn == alias_fqn
418+
assert result[0].fromColumns[0].root == src_fqn
419+
assert result[0].toColumn.root == alias_fqn
424420

425421
@pytest.mark.order(11)
426422
def test_build_column_lineage_from_parser_multi_table_filters_correctly(self):
@@ -434,19 +430,15 @@ def test_build_column_lineage_from_parser_multi_table_filters_correctly(self):
434430
# Column from the correct upstream table
435431
src_col_correct = MagicMock()
436432
src_col_correct.raw_name = "id"
437-
src_col_correct._parent = MagicMock()
438-
src_col_correct._parent.__str__ = MagicMock(
439-
return_value="relation_table"
440-
)
433+
src_col_correct._parent = type("_FakeTable", (), {"__str__": lambda self: "relation_table"})()
441434

442435
tgt_col_correct = MagicMock()
443436
tgt_col_correct.raw_name = "relation_id"
444437

445438
# Column from a DIFFERENT table with same name 'id'
446439
src_col_wrong = MagicMock()
447440
src_col_wrong.raw_name = "id"
448-
src_col_wrong._parent = MagicMock()
449-
src_col_wrong._parent.__str__ = MagicMock(return_value="other_table")
441+
src_col_wrong._parent = type("_FakeTable", (), {"__str__": lambda self: "other_table"})()
450442

451443
tgt_col_wrong = MagicMock()
452444
tgt_col_wrong.raw_name = "other_relation_id"
@@ -464,66 +456,23 @@ def test_build_column_lineage_from_parser_multi_table_filters_correctly(self):
464456
mock_from_entity.name.root = "relation_table"
465457
mock_data_model = MagicMock()
466458

467-
with patch(
468-
"metadata.ingestion.source.dashboard.quicksight.metadata.get_column_fqn",
469-
return_value=src_fqn,
470-
):
471-
with patch.object(
459+
with (
460+
patch(
461+
"metadata.ingestion.source.dashboard.quicksight.metadata.get_column_fqn",
462+
return_value=src_fqn,
463+
),
464+
patch.object(
472465
self.quicksight,
473466
"_get_data_model_column_fqn",
474467
return_value=alias_fqn,
475-
):
476-
result = self.quicksight._build_column_lineage_from_parser(
477-
mock_parser, mock_from_entity, mock_data_model
478-
)
468+
),
469+
):
470+
result = self.quicksight._build_column_lineage_from_parser(mock_parser, mock_from_entity, mock_data_model)
479471

480472
# Only 1 result — the wrong table's column must be filtered out
481473
assert len(result) == 1
482-
assert result[0].fromColumns == [src_fqn]
483-
assert result[0].toColumn == alias_fqn
484-
485-
@pytest.mark.order(11)
486-
def test_build_column_lineage_no_fallback_when_parser_has_global_lineage(self):
487-
"""
488-
Regression test for the multi-table fallback bug (Issue #26670).
489-
490-
When lineage_parser.column_lineage is non-empty (parser succeeded)
491-
but none of the pairs match from_entity (because they belong to a
492-
different upstream table in a multi-table JOIN), the method must
493-
return an empty list and must NOT call _get_column_lineage (the
494-
name-based fallback). Calling the fallback here would manufacture
495-
incorrect cross-table column lineage.
496-
"""
497-
# Parser found lineage for a DIFFERENT table, not our from_entity
498-
other_src_col = MagicMock()
499-
other_src_col.raw_name = "user_id"
500-
other_src_col._parent = MagicMock()
501-
other_src_col._parent.__str__ = MagicMock(return_value="users_table")
502-
503-
other_tgt_col = MagicMock()
504-
other_tgt_col.raw_name = "uid"
505-
506-
mock_parser = MagicMock()
507-
# Parser globally found lineage — but only for 'users_table'
508-
mock_parser.column_lineage = [(other_src_col, other_tgt_col)]
509-
510-
mock_from_entity = MagicMock()
511-
# Our from_entity is 'orders_table' — no parser pairs match it
512-
mock_from_entity.name.root = "orders_table"
513-
mock_data_model = MagicMock()
514-
515-
with patch.object(
516-
self.quicksight,
517-
"_get_column_lineage",
518-
) as mock_fallback:
519-
result = self.quicksight._build_column_lineage_from_parser(
520-
mock_parser, mock_from_entity, mock_data_model
521-
)
522-
523-
# Must NOT have called the name-based fallback
524-
mock_fallback.assert_not_called()
525-
# Must return an empty list — no manufactured lineage
526-
assert result == []
474+
assert result[0].fromColumns[0].root == src_fqn
475+
assert result[0].toColumn.root == alias_fqn
527476

528477
@pytest.mark.order(12)
529478
def test_build_column_lineage_no_fallback_when_parser_has_global_lineage(self):
@@ -559,9 +508,7 @@ def test_build_column_lineage_no_fallback_when_parser_has_global_lineage(self):
559508
self.quicksight,
560509
"_get_column_lineage",
561510
) as mock_fallback:
562-
result = self.quicksight._build_column_lineage_from_parser(
563-
mock_parser, mock_from_entity, mock_data_model
564-
)
511+
result = self.quicksight._build_column_lineage_from_parser(mock_parser, mock_from_entity, mock_data_model)
565512

566513
# Must NOT have called the name-based fallback
567514
mock_fallback.assert_not_called()
@@ -578,9 +525,7 @@ def test_build_column_lineage_from_parser_iterable_parent(self):
578525
"""
579526
# Parent is an iterable (list) of Table objects
580527
parent_table_mock = MagicMock()
581-
parent_table_mock.__str__ = MagicMock(
582-
return_value="relation_table"
583-
)
528+
parent_table_mock.__str__ = MagicMock(return_value="relation_table")
584529

585530
src_col = MagicMock()
586531
src_col.raw_name = "id"
@@ -600,26 +545,26 @@ def test_build_column_lineage_from_parser_iterable_parent(self):
600545
mock_from_entity.name.root = "relation_table"
601546
mock_data_model = MagicMock()
602547

603-
with patch(
604-
"metadata.ingestion.source.dashboard.quicksight.metadata.get_column_fqn",
605-
return_value=src_fqn,
606-
):
607-
with patch.object(
548+
with (
549+
patch(
550+
"metadata.ingestion.source.dashboard.quicksight.metadata.get_column_fqn",
551+
return_value=src_fqn,
552+
),
553+
patch.object(
608554
self.quicksight,
609555
"_get_data_model_column_fqn",
610556
return_value=alias_fqn,
611-
):
612-
result = (
613-
self.quicksight._build_column_lineage_from_parser(
614-
mock_parser,
615-
mock_from_entity,
616-
mock_data_model,
617-
)
618-
)
557+
),
558+
):
559+
result = self.quicksight._build_column_lineage_from_parser(
560+
mock_parser,
561+
mock_from_entity,
562+
mock_data_model,
563+
)
619564

620565
assert len(result) == 1
621-
assert result[0].fromColumns == [src_fqn]
622-
assert result[0].toColumn == alias_fqn
566+
assert result[0].fromColumns[0].root == src_fqn
567+
assert result[0].toColumn.root == alias_fqn
623568

624569
@pytest.mark.order(14)
625570
def test_build_column_lineage_from_parser_falls_back_when_empty(self):
@@ -649,12 +594,8 @@ def test_build_column_lineage_from_parser_falls_back_when_empty(self):
649594
"_get_column_lineage",
650595
return_value=fallback_lineage,
651596
) as mock_get_col_lineage:
652-
result = self.quicksight._build_column_lineage_from_parser(
653-
mock_parser, mock_from_entity, mock_data_model
654-
)
597+
result = self.quicksight._build_column_lineage_from_parser(mock_parser, mock_from_entity, mock_data_model)
655598

656599
# Verify fallback was called with correct column names
657-
mock_get_col_lineage.assert_called_once_with(
658-
mock_from_entity, mock_data_model, ["col_a"]
659-
)
600+
mock_get_col_lineage.assert_called_once_with(mock_from_entity, mock_data_model, ["col_a"])
660601
assert result is fallback_lineage

0 commit comments

Comments
 (0)