Skip to content

Commit 9bb45db

Browse files
authored
Fix: Traverse lineage tool (#105)
* fix: lineage tool to only return default attributes * fix: update the traverse lineage tool on server with attributes in the docstring * fix: remove redundant step * fix: redundant check
1 parent 549a3b8 commit 9bb45db

File tree

2 files changed

+87
-42
lines changed

2 files changed

+87
-42
lines changed

modelcontextprotocol/server.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -322,40 +322,42 @@ def traverse_lineage_tool(
322322
depth=1000000,
323323
size=10,
324324
immediate_neighbors=True,
325+
include_attributes=None,
325326
):
326327
"""
327328
Traverse asset lineage in specified direction.
328329
330+
By default, essential attributes are included in results. Additional attributes can be
331+
specified via include_attributes parameter for richer lineage information.
332+
329333
Args:
330334
guid (str): GUID of the starting asset
331335
direction (str): Direction to traverse ("UPSTREAM" or "DOWNSTREAM")
332336
depth (int, optional): Maximum depth to traverse. Defaults to 1000000.
333337
size (int, optional): Maximum number of results to return. Defaults to 10.
334338
immediate_neighbors (bool, optional): Only return immediate neighbors. Defaults to True.
339+
include_attributes (List[str], optional): List of additional attribute names to include in results.
340+
These will be added to the default set.
341+
342+
Default Attributes (always included):
343+
- name, display_name, description, qualified_name, user_description
344+
- certificate_status, owner_users, owner_groups
345+
- connector_name, has_lineage, source_created_at, source_updated_at
346+
- readme, asset_tags
335347
336348
Returns:
337349
Dict[str, Any]: Dictionary containing:
338-
- assets: List of assets in the lineage
339-
- references: List of dictionaries containing:
340-
- source_guid: GUID of the source asset
341-
- target_guid: GUID of the target asset
342-
- direction: Direction of the reference (upstream/downstream)
350+
- assets: List of assets in the lineage with processed attributes
351+
- error: None if no error occurred, otherwise the error message
343352
344-
Example:
345-
# Get lineage with specific depth and size
353+
Examples:
354+
# Get lineage with default attributes
346355
lineage = traverse_lineage_tool(
347356
guid="asset-guid-here",
348357
direction="DOWNSTREAM",
349-
depth=1000000,
358+
depth=1000,
350359
size=10
351360
)
352-
353-
# Access assets and their references
354-
for asset in lineage["assets"]:
355-
print(f"Asset: {asset.guid}")
356-
357-
for ref in lineage["references"]:
358-
print(f"Reference: {ref['source_guid']} -> {ref['target_guid']}")
359361
"""
360362
try:
361363
direction_enum = LineageDirection[direction.upper()]
@@ -364,12 +366,16 @@ def traverse_lineage_tool(
364366
f"Invalid direction: {direction}. Must be either 'UPSTREAM' or 'DOWNSTREAM'"
365367
)
366368

369+
# Parse include_attributes parameter if provided
370+
parsed_include_attributes = parse_list_parameter(include_attributes)
371+
367372
return traverse_lineage(
368373
guid=guid,
369374
direction=direction_enum,
370-
depth=depth,
371-
size=size,
372-
immediate_neighbors=immediate_neighbors,
375+
depth=int(depth),
376+
size=int(size),
377+
immediate_neighbors=bool(immediate_neighbors),
378+
include_attributes=parsed_include_attributes,
373379
)
374380

375381

modelcontextprotocol/tools/lineage.py

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import logging
2-
from typing import Dict, Any
2+
from typing import Dict, Any, List, Optional, Union
33

44
from client import get_atlan_client
55
from pyatlan.model.enums import LineageDirection
66
from pyatlan.model.lineage import FluentLineage
7+
from pyatlan.model.fields.atlan_fields import AtlanField
8+
from utils.search import SearchUtils
9+
from utils.constants import DEFAULT_SEARCH_ATTRIBUTES
710

811
# Configure logging
912
logger = logging.getLogger(__name__)
@@ -15,61 +18,97 @@ def traverse_lineage(
1518
depth: int = 1000000,
1619
size: int = 10,
1720
immediate_neighbors: bool = False,
21+
include_attributes: Optional[List[Union[str, AtlanField]]] = None,
1822
) -> Dict[str, Any]:
1923
"""
2024
Traverse asset lineage in specified direction.
2125
26+
By default, essential attributes used in search operations are included.
27+
Additional attributes can be specified via include_attributes parameter.
28+
2229
Args:
2330
guid (str): GUID of the starting asset
2431
direction (LineageDirection): Direction to traverse (UPSTREAM or DOWNSTREAM)
2532
depth (int, optional): Maximum depth to traverse. Defaults to 1000000.
2633
size (int, optional): Maximum number of results to return. Defaults to 10.
27-
immediate_neighbors (bool, optional): Only return immediate neighbors. Defaults to True.
34+
immediate_neighbors (bool, optional): Only return immediate neighbors. Defaults to False.
35+
include_attributes (Optional[List[Union[str, AtlanField]]], optional): List of additional
36+
attributes to include in results. Can be string attribute names or AtlanField objects.
37+
These will be added to the default set. Defaults to None.
2838
2939
Returns:
3040
Dict[str, Any]: Dictionary containing:
31-
- assets: List of assets in the lineage
32-
- references: List of dictionaries containing:
33-
- source_guid: GUID of the source asset
34-
- target_guid: GUID of the target asset
35-
- direction: Direction of the reference (upstream/downstream)
41+
- assets: List of assets in the lineage with processed attributes
42+
- error: None if no error occurred, otherwise the error message
3643
3744
Raises:
3845
Exception: If there's an error executing the lineage request
3946
"""
40-
logger.info(f"Starting lineage traversal from {guid} in direction {direction}")
47+
logger.info(
48+
f"Starting lineage traversal from {guid} in direction {direction}, "
49+
f"depth={depth}, size={size}, immediate_neighbors={immediate_neighbors}"
50+
)
51+
logger.debug(f"Include attributes parameter: {include_attributes}")
4152

4253
try:
4354
# Initialize base request
44-
request = (
55+
logger.debug("Initializing FluentLineage object")
56+
lineage_builder = (
4557
FluentLineage(starting_guid=guid)
4658
.direction(direction)
4759
.depth(depth)
4860
.size(size)
4961
.immediate_neighbors(immediate_neighbors)
50-
.request
5162
)
5263

64+
# Prepare attributes to include: default attributes + additional user-specified attributes
65+
all_attributes = DEFAULT_SEARCH_ATTRIBUTES.copy()
66+
67+
if include_attributes:
68+
logger.debug(f"Adding user-specified attributes: {include_attributes}")
69+
for attr in include_attributes:
70+
if isinstance(attr, str) and attr not in all_attributes:
71+
all_attributes.append(attr)
72+
73+
logger.debug(f"Total attributes to include: {all_attributes}")
74+
75+
# Include all string attributes in results
76+
for attr_name in all_attributes:
77+
attr_obj = SearchUtils._get_asset_attribute(attr_name)
78+
if attr_obj is None:
79+
logger.warning(
80+
f"Unknown attribute for inclusion: {attr_name}, skipping"
81+
)
82+
continue
83+
logger.debug(f"Including attribute: {attr_name}")
84+
lineage_builder = lineage_builder.include_on_results(attr_obj)
85+
5386
# Execute request
54-
logger.debug("Executing lineage request")
55-
client = get_atlan_client()
56-
response = client.asset.get_lineage_list(request) # noqa: F821
87+
logger.debug("Converting FluentLineage to request object")
88+
request = lineage_builder.request
5789

58-
# Process results
59-
result = {"assets": []}
90+
logger.info("Executing lineage request")
91+
client = get_atlan_client()
92+
response = client.asset.get_lineage_list(request)
6093

61-
# Handle None response
94+
# Process results using same pattern as search
95+
logger.info("Processing lineage results")
6296
if response is None:
6397
logger.info("No lineage results found")
64-
return result
98+
return {"assets": [], "error": None}
99+
100+
# Convert results to list and process using Pydantic serialization
101+
results_list = [
102+
result.dict(by_alias=True, exclude_unset=True)
103+
for result in response
104+
if result is not None
105+
]
106+
107+
logger.info(
108+
f"Lineage traversal completed, returned {len(results_list)} results"
109+
)
110+
return {"assets": results_list, "error": None}
65111

66-
assets = []
67-
for item in response:
68-
if item is None:
69-
continue
70-
assets.append(item)
71-
result["assets"] = assets
72-
return result
73112
except Exception as e:
74113
logger.error(f"Error traversing lineage: {str(e)}")
75114
return {"assets": [], "error": str(e)}

0 commit comments

Comments
 (0)