Skip to content

Commit 612049f

Browse files
authored
Merge pull request #43 from atlanhq/link-terms
Update code to link terms to assets
2 parents e94ed3f + fff3d1d commit 612049f

File tree

8 files changed

+3080
-47
lines changed

8 files changed

+3080
-47
lines changed

HISTORY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.0.26 (May 4, 2023)
2+
3+
* Add remove_terms method to AtlanClient
4+
* Add append_terms method to AtlanClient
5+
* Add replace_terms method to AtlanClient
6+
17
## 0.0.25 (May 2, 2023)
28

39
* Update create method for Readme asset

pyatlan/client/atlan.py

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,7 @@ def get_asset_by_qualified_name(
313313
raw_json["entity"]["attributes"].update(
314314
raw_json["entity"]["relationshipAttributes"]
315315
)
316-
asset = AssetResponse[A](**raw_json).entity
317-
asset.is_incomplete = False
316+
asset = self.handle_relationships(raw_json)
318317
if not isinstance(asset, asset_type):
319318
raise NotFoundError(
320319
message=f"Asset with qualifiedName {qualified_name} "
@@ -345,16 +344,7 @@ def get_asset_by_guid(
345344
GET_ENTITY_BY_GUID.format_path_with_params(guid),
346345
query_params,
347346
)
348-
if (
349-
"relationshipAttributes" in raw_json["entity"]
350-
and raw_json["entity"]["relationshipAttributes"]
351-
):
352-
raw_json["entity"]["attributes"].update(
353-
raw_json["entity"]["relationshipAttributes"]
354-
)
355-
raw_json["entity"]["relationshipAttributes"] = {}
356-
asset = AssetResponse[A](**raw_json).entity
357-
asset.is_incomplete = False
347+
asset = self.handle_relationships(raw_json)
358348
if not isinstance(asset, asset_type):
359349
raise NotFoundError(
360350
message=f"Asset with GUID {guid} is not of the type requested: {asset_type.__name__}.",
@@ -366,6 +356,19 @@ def get_asset_by_guid(
366356
raise NotFoundError(message=ae.user_message, code=ae.code) from ae
367357
raise ae
368358

359+
def handle_relationships(self, raw_json):
360+
if (
361+
"relationshipAttributes" in raw_json["entity"]
362+
and raw_json["entity"]["relationshipAttributes"]
363+
):
364+
raw_json["entity"]["attributes"].update(
365+
raw_json["entity"]["relationshipAttributes"]
366+
)
367+
raw_json["entity"]["relationshipAttributes"] = {}
368+
asset = AssetResponse[A](**raw_json).entity
369+
asset.is_incomplete = False
370+
return asset
371+
369372
@validate_arguments()
370373
def retrieve_minimal(self, guid: str, asset_type: Type[A]) -> A:
371374
return self.get_asset_by_guid(
@@ -602,3 +605,100 @@ def replace_custom_metadata(self, guid: str, custom_metadata: CustomMetadata):
602605
None,
603606
custom_metadata_request,
604607
)
608+
609+
@validate_arguments()
610+
def append_terms(
611+
self,
612+
asset_type: Type[A],
613+
terms: list[AtlasGlossaryTerm],
614+
guid: Optional[str] = None,
615+
qualified_name: Optional[str] = None,
616+
) -> A:
617+
if guid:
618+
if qualified_name:
619+
raise ValueError(
620+
"Either guid or qualified_name can be be specified not both"
621+
)
622+
asset = self.get_asset_by_guid(guid=guid, asset_type=asset_type)
623+
elif qualified_name:
624+
asset = self.get_asset_by_qualified_name(
625+
qualified_name=qualified_name, asset_type=asset_type
626+
)
627+
else:
628+
raise ValueError("Either guid or qualified name must be specified")
629+
if not terms:
630+
return asset
631+
replacement_terms: list[AtlasGlossaryTerm] = []
632+
if existing_terms := asset.terms:
633+
replacement_terms.extend(
634+
term for term in existing_terms if term.relationship_status != "DELETED"
635+
)
636+
replacement_terms.extend(terms)
637+
asset.terms = replacement_terms
638+
response = self.upsert(entity=asset)
639+
if assets := response.assets_updated(asset_type=asset_type):
640+
return assets[0]
641+
return asset
642+
643+
@validate_arguments()
644+
def replace_terms(
645+
self,
646+
asset_type: Type[A],
647+
terms: list[AtlasGlossaryTerm],
648+
guid: Optional[str] = None,
649+
qualified_name: Optional[str] = None,
650+
) -> A:
651+
if guid:
652+
if qualified_name:
653+
raise ValueError(
654+
"Either guid or qualified_name can be be specified not both"
655+
)
656+
asset = self.get_asset_by_guid(guid=guid, asset_type=asset_type)
657+
elif qualified_name:
658+
asset = self.get_asset_by_qualified_name(
659+
qualified_name=qualified_name, asset_type=asset_type
660+
)
661+
else:
662+
raise ValueError("Either guid or qualified name must be specified")
663+
asset.terms = terms
664+
response = self.upsert(entity=asset)
665+
if assets := response.assets_updated(asset_type=asset_type):
666+
return assets[0]
667+
return asset
668+
669+
@validate_arguments()
670+
def remove_terms(
671+
self,
672+
asset_type: Type[A],
673+
terms: list[AtlasGlossaryTerm],
674+
guid: Optional[str] = None,
675+
qualified_name: Optional[str] = None,
676+
) -> A:
677+
if not terms:
678+
raise ValueError("A list of terms to remove must be specified")
679+
if guid:
680+
if qualified_name:
681+
raise ValueError(
682+
"Either guid or qualified_name can be be specified not both"
683+
)
684+
asset = self.get_asset_by_guid(guid=guid, asset_type=asset_type)
685+
elif qualified_name:
686+
asset = self.get_asset_by_qualified_name(
687+
qualified_name=qualified_name, asset_type=asset_type
688+
)
689+
else:
690+
raise ValueError("Either guid or qualified name must be specified")
691+
replacement_terms: list[AtlasGlossaryTerm] = []
692+
guids_to_be_removed = {t.guid for t in terms}
693+
if existing_terms := asset.terms:
694+
replacement_terms.extend(
695+
term
696+
for term in existing_terms
697+
if term.relationship_status != "DELETED"
698+
and term.guid not in guids_to_be_removed
699+
)
700+
asset.terms = replacement_terms
701+
response = self.upsert(entity=asset)
702+
if assets := response.assets_updated(asset_type=asset_type):
703+
return assets[0]
704+
return asset

pyatlan/generator/templates/entity.jinja2

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,15 @@ def validate_required_fields(field_names:list[str], values:list[Any]):
7171
raise ValueError(f"{field_name} is required")
7272
if isinstance(value, str) and not value.strip():
7373
raise ValueError(f"{field_name} cannot be blank")
74-
{%- macro gen_properties(attribute_defs) %}
74+
{%- macro gen_properties(attribute_defs, additional_names=[]) %}
7575
_convience_properties: ClassVar[list[str]] = [
7676
{%- for attribute_def in attribute_defs %}
7777
"{{ attribute_def.name | to_snake_case }}",
78-
{%- endfor %}]
78+
{%- endfor %}
79+
{%- for name in additional_names %}
80+
"{{ name }}",
81+
{%- endfor %}]
82+
7983
{%- for attribute_def in attribute_defs %}
8084
{%- set type = attribute_def.typeName | get_type %}
8185
{%- set property_type %}{% if attribute_def.isOptional %}Optional[{% endif %}{{type}}{% if attribute_def.isOptional %}]{% endif %}{% endset %}
@@ -107,7 +111,19 @@ class {{ entity_def.name }}({{super_classes[0]}} {%- if "Asset" in super_classes
107111
return object.__setattr__(self, name, value)
108112
super().__setattr__( name, value)
109113

110-
{{ gen_properties(entity_def.attribute_defs) }}
114+
{{ gen_properties(entity_def.attribute_defs, ["terms"]) }}
115+
116+
@property
117+
def terms(self) -> list[AtlasGlossaryTerm]:
118+
if self.attributes is None:
119+
self.attributes = self.Attributes()
120+
return [] if self.attributes.meanings is None else self.attributes.meanings
121+
122+
@terms.setter
123+
def terms(self, terms: list[AtlasGlossaryTerm]):
124+
if self.attributes is None:
125+
self.attributes = self.Attributes()
126+
self.attributes.meanings = terms
111127

112128
{%- if entity_def.name == "Referenceable" %}
113129

0 commit comments

Comments
 (0)