Skip to content

Commit 4401707

Browse files
committed
[feat] Added AtlanRequest which handles retranslators (human-readable to hashed names)
- Initial implementation of `AtlanRequest` to handle retranslators. - Added `AtlanTagRetranslator` for tag name retranslation.
1 parent 42eb2da commit 4401707

5 files changed

Lines changed: 109 additions & 17 deletions

File tree

pyatlan/client/atlan.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
Purpose,
8282
)
8383
from pyatlan.model.atlan_image import AtlanImage
84-
from pyatlan.model.core import Announcement, AtlanObject, AtlanResponse
84+
from pyatlan.model.core import Announcement, AtlanObject, AtlanRequest, AtlanResponse
8585
from pyatlan.model.custom_metadata import CustomMetadataDict
8686
from pyatlan.model.enums import AtlanConnectorType, AtlanTypeCategory, CertificateStatus
8787
from pyatlan.model.group import AtlanGroup, CreateGroupResponse, GroupResponse
@@ -510,10 +510,13 @@ def _call_api_internal(
510510
if text_response:
511511
response_ = response.text
512512
else:
513-
response_ = events if events else response.json()
514-
response_ = AtlanResponse(
515-
raw_json=response.json(), client=self
516-
).to_dict()
513+
response_ = (
514+
events
515+
if events
516+
else AtlanResponse(
517+
raw_json=response.json(), client=self
518+
).to_dict()
519+
)
517520
LOGGER.debug("response: %s", response_)
518521
return response_
519522
except (
@@ -692,9 +695,7 @@ def _create_params(
692695
params["params"] = query_params
693696
if request_obj is not None:
694697
if isinstance(request_obj, AtlanObject):
695-
params["data"] = request_obj.json(
696-
by_alias=True, exclude_unset=exclude_unset
697-
)
698+
params["data"] = AtlanRequest(request_obj, client=self).json()
698699
elif api.consumes == APPLICATION_ENCODED_FORM:
699700
params["data"] = request_obj
700701
else:

pyatlan/model/core.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from pyatlan.model.constants import DELETED_, DELETED_SENTINEL
2626
from pyatlan.model.enums import AnnouncementType, EntityStatus, SaveSemantic
27+
from pyatlan.model.retranslators import AtlanTagRetranslator
2728
from pyatlan.model.structs import SourceTagAttachment
2829
from pyatlan.model.translators import AtlanTagTranslator
2930

@@ -103,13 +104,13 @@ def _convert_to_tag_name(cls, data):
103104
return data
104105
return AtlanTagName(data) if data else cls.get_deleted_sentinel()
105106

106-
@staticmethod
107-
def json_encode_atlan_tag(atlan_tag_name: "AtlanTagName"):
108-
from pyatlan.client.atlan import AtlanClient
107+
# @staticmethod
108+
# def json_encode_atlan_tag(atlan_tag_name: "AtlanTagName"):
109+
# from pyatlan.client.atlan import AtlanClient
109110

110-
return AtlanClient.get_current_client().atlan_tag_cache.get_id_for_name(
111-
atlan_tag_name._display_text
112-
)
111+
# return AtlanClient.get_current_client().atlan_tag_cache.get_id_for_name(
112+
# atlan_tag_name._display_text
113+
# )
113114

114115

115116
class AtlanObject(BaseModel):
@@ -219,6 +220,33 @@ def to_dict(self) -> Dict[str, Any]:
219220
return self.translated
220221

221222

223+
class AtlanRequest:
224+
def __init__(self, model: BaseModel, client: "AtlanClient"):
225+
self.client = client
226+
self.model = model
227+
self.retranslators = [
228+
AtlanTagRetranslator(client),
229+
# add others...
230+
]
231+
# Do: model.json() → parse → translate → store
232+
raw_json = self.model.json(by_alias=True, exclude_unset=True)
233+
parsed = json.loads(raw_json)
234+
self.translated = self._deep_retranslate(parsed)
235+
236+
def _deep_retranslate(self, data: Any) -> Any:
237+
if isinstance(data, dict):
238+
for retranslator in self.retranslators:
239+
if retranslator.applies_to(data):
240+
data = retranslator.retranslate(data)
241+
return {key: self._deep_retranslate(value) for key, value in data.items()}
242+
elif isinstance(data, list):
243+
return [self._deep_retranslate(item) for item in data]
244+
return data
245+
246+
def json(self, **kwargs) -> str:
247+
return json.dumps(self.translated, **kwargs)
248+
249+
222250
class SearchRequest(AtlanObject, ABC):
223251
attributes: Optional[List[str]] = Field(
224252
default_factory=list,

pyatlan/model/retranslators.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from __future__ import annotations
2+
3+
from abc import ABC, abstractmethod
4+
from typing import TYPE_CHECKING, Any, Dict
5+
6+
if TYPE_CHECKING:
7+
from pyatlan.client.atlan import AtlanClient
8+
9+
10+
class BaseRetranslator(ABC):
11+
@abstractmethod
12+
def applies_to(self, data: Dict[str, Any]) -> bool:
13+
pass
14+
15+
@abstractmethod
16+
def retranslate(self, data: Dict[str, Any]) -> Dict[str, Any]:
17+
pass
18+
19+
20+
class AtlanTagRetranslator(BaseRetranslator):
21+
_TYPE_NAME = "typeName"
22+
_CLASSIFICATIONS = "classifications"
23+
_CLASSIFICATION_NAMES = "classificationNames"
24+
_SOURCE_ATTACHMENTS = "source_tag_attachements"
25+
26+
def __init__(self, client: "AtlanClient"):
27+
self.client = client
28+
29+
def applies_to(self, data: Dict[str, Any]) -> bool:
30+
return self._CLASSIFICATION_NAMES in data or self._CLASSIFICATIONS in data
31+
32+
def retranslate(self, data: Dict[str, Any]) -> Dict[str, Any]:
33+
data = data.copy()
34+
35+
if self._CLASSIFICATION_NAMES in data:
36+
data[self._CLASSIFICATION_NAMES] = [
37+
self.client.atlan_tag_cache.get_id_for_name(str(name))
38+
for name in data[self._CLASSIFICATION_NAMES]
39+
]
40+
41+
if self._CLASSIFICATIONS in data:
42+
for classification in data[self._CLASSIFICATIONS]:
43+
tag_name = str(classification.get(self._TYPE_NAME))
44+
if tag_name:
45+
tag_id = self.client.atlan_tag_cache.get_id_for_name(tag_name)
46+
classification[self._TYPE_NAME] = tag_id
47+
48+
# Rebuild source tag structure if `source_tag_attachements` are present
49+
attachments = classification.pop(self._SOURCE_ATTACHMENTS, None)
50+
if attachments:
51+
attr_id = self.client.atlan_tag_cache.get_source_tags_attr_id(
52+
str(tag_id)
53+
)
54+
if attr_id:
55+
classification.setdefault("attributes", {})[attr_id] = [
56+
{
57+
"typeName": "SourceTagAttachment",
58+
"attributes": attachment.dict(),
59+
}
60+
for attachment in attachments
61+
]
62+
63+
return data

pyatlan/model/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def encoders():
2121

2222
return {
2323
datetime: lambda v: int(v.timestamp() * 1000),
24-
AtlanTagName: AtlanTagName.json_encode_atlan_tag,
24+
AtlanTagName: lambda atn: atn._display_text,
2525
}
2626

2727

tests/unit/test_atlan_tag_name.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ def test_convert_to_tag_name(current_client: AtlanClient, monkeypatch):
9191
assert str(sut) == ATLAN_TAG_ID
9292

9393

94-
def test_json_encode_atlan_tag(good_atlan_tag):
95-
assert AtlanTagName.json_encode_atlan_tag(good_atlan_tag) == ATLAN_TAG_ID
94+
# def test_json_encode_atlan_tag(good_atlan_tag):
95+
# assert AtlanTagName.json_encode_atlan_tag(good_atlan_tag) == ATLAN_TAG_ID
9696

9797

9898
def test_asset_tag_name_field_deserialization(current_client: AtlanClient, monkeypatch):

0 commit comments

Comments
 (0)