Viewing history of asset
Accessing the history of an asset in Atlan is a flexible operation. This also makes it a bit more complex to understand than the other operations. To encapsulate the full flexibility of Atlan's search, the SDK provides a dedicated AuditSearchRequest object.
Atlan's audit log that contains the history of an asset uses Elasticsearch. This makes the approach you use to access history similar to searching. However, there are differences as the audit log uses a different index than the broader search. If you're feeling brave, feel free to experiment with the more complex search mechanisms outlined in the searching section. But this should be sufficient to get you started with accessing asset history.
Build request
To retrieve an asset's history in Atlan, you need to define the request. For simplicity, we provide helper methods to retrieve a defined number of entries in reverse-chronological order (most recent entries first).
By GUID
To request the history of an asset by GUID:
- Java
- Python
- Kotlin
- Raw REST API
AuditSearchRequest request = AuditSearchRequest.byGuid( // (1)
client, // (2)
"6fc01478-1263-42ae-b8ca-c4a57da51392", // (3)
10) // (4)
.build(); // (5)
- Create a request for the history of an asset, by its GUID.
- Because this operation will directly look up the asset's history in Atlan, you must provide it an
AtlanClientthrough which to connect to the tenant. - Specify the GUID of the asset.
- Specify the amount of history (maximum number of activities). This will be in reverse-chronological order (most recent entries first).
- Build the request.
import logging
from pyatlan.model.audit import CustomMetadataAttributesAuditDetail
from pyatlan.model.assets import Table
from pyatlan.model.core import AtlanTag
from pyatlan.client.atlan import AtlanClient
from pyatlan.client.audit import AuditSearchRequest
from pyatlan.model.search import SortItem
from pyatlan.model.enums import SortOrder
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.INFO)
client = AtlanClient()
request = AuditSearchRequest.by_guid( # (1)
guid="6fc01478-1263-42ae-b8ca-c4a57da51392", # (2)
size=10, # (3)
sort=[SortItem("created", order=SortOrder.DESCENDING)] # (4)
)
- Create a request for the history of an asset, by its GUID.
- Specify the GUID of the asset.
- (optional) Specify the amount of history (maximum number of activities). Defaults to
10. - (optional) Specify sorting criteria for the results. By default, it sorts in reverse-chronological order, with the most recent entries first.
val request = AuditSearchRequest.byGuid( // (1)
client, // (2)
"6fc01478-1263-42ae-b8ca-c4a57da51392", // (3)
10 // (4)
).build() // (5)
- Create a request for the history of an asset, by its GUID.
- Because this operation will directly look up the asset's history in Atlan, you must provide it an
AtlanClientthrough which to connect to the tenant. - Specify the GUID of the asset.
- Specify the amount of history (maximum number of activities). This will be in reverse-chronological order (most recent entries first).
- Build the request.
"filter": [ // (1)
}
}
]
- To retrieve history for a specific asset by that asset's GUID, start with a filter.
- Within the filter run a term query.
- And specifically filter by the field
entityIdin the index.
By qualifiedName
To request the history of an asset by qualifiedName:
- Java
- Python
- Kotlin
- Raw REST API
AuditSearchRequest request = AuditSearchRequest.byQualifiedName( // (1)
client, // (2)
Glossary.TYPE_NAME, "FzCMyPR2LxkPFgr8eNGrq", // (3)
10) // (4)
.build(); // (5)
- Create a request for the history of an asset, by its qualifiedName.
- Because this operation will directly look up the asset's history in Atlan, you must provide it an
AtlanClientthrough which to connect to the tenant. - Specify the type of the asset and qualifiedName of the asset.
- Specify the amount of history (maximum number of activities). This will be in reverse-chronological order (most recent entries first).
- Build the request.
import logging
from pyatlan.model.audit import CustomMetadataAttributesAuditDetail
from pyatlan.model.assets import Table
from pyatlan.model.core import AtlanTag
from pyatlan.client.atlan import AtlanClient
from pyatlan.client.audit import AuditSearchRequest
from pyatlan.model.search import SortItem
from pyatlan.model.enums import SortOrder
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.INFO)
client = AtlanClient()
request = AuditSearchRequest.by_qualified_name( # (1)
type_name="AtlasGlossary", # (2)
qualified_name="FzCMyPR2LxkPFgr8eNGrq", # (3)
size=10, # (4)
sort=[SortItem("created", order=SortOrder.DESCENDING)] # (5)
)
- Create a request for the history of an asset, by its qualifiedName.
- Specify the type of the asset
- Specify the qualifiedName of the asset.
- (optional) Specify the amount of history (maximum number of activities). Defaults to
10. - (optional) Specify sorting criteria for the results. By default, it sorts in reverse-chronological order, with the most recent entries first.
val request = AuditSearchRequest.byQualifiedName( // (1)
client, // (2)
Glossary.TYPE_NAME, "FzCMyPR2LxkPFgr8eNGrq", // (3)
10 // (4)
).build() // (5)
- Create a request for the history of an asset, by its qualifiedName.
- Because this operation will directly look up the asset's history in Atlan, you must provide it an
AtlanClientthrough which to connect to the tenant. - Specify the type of the asset and qualifiedName of the asset.
- Specify the amount of history (maximum number of activities). This will be in reverse-chronological order (most recent entries first).
- Build the request.
"must": [ // (1)
}
},
{
"term": {
"typeName": { // (4)
"value": "AtlasGlossary"
}
}
}
]
- To retrieve history for a specific asset by that asset's qualifiedName, you need to combine several conditions.
- You need a term query for both conditions.
- One condition you must provide is for the
entityQualifiedName, giving the qualifiedName of the asset for which you want to retrieve history. - You also need to define the
typeNameof the asset for which you want to retrieve history, when retrieving by qualifiedName.
Run search
To now run the search, we call the search() method against our request object:
- Java
- Python
- Kotlin
- Raw REST API
AuditSearchResponse response = request.search(client);
log.info(response.getCount()); // (1)
List<EntityAudit> results = response.getEntityAudits(); // (2)
- The
getCount()method gives the total number of activities. Note that this could be smaller than the number requested, if fewer activities have occurred against the asset than the number used in the request. - The details of each activity can be accessed through the
getEntityAudits()method on the response.
response = client.audit.search(criteria=request, bulk=False) #(1)
LOGGER.info(response.total_count) # (2)
-
client.audit.search()method takes following parameters:criteria: defines the search query to execute the search.bulk(default: False): specifies whether to execute the search in audit bulk mode for retrieving the history of assets matching the criteria. This mode is optimized for handling large results (more than10,000). When enabled (True), the results will be reordered based on the creation timestamp to facilitate iterating through large datasets.
note
If the number of results exceeds the predefined threshold
(10,000 assets) audit search will be automatically converted into a bulk audit search.
:::
2. The total_count property gives the total number of activities. Note that this could be smaller than the number requested, if fewer activities have occurred against the asset than the number used in the request.
val response: AuditSearchResponse = request.search(client)
log.info(response.count) // (1)
val results = response.entityAudits // (2)
- The
.countmember gives the total number of activities. Note that this could be smaller than the number requested, if fewer activities have occurred against the asset than the number used in the request. - The details of each activity can be accessed through the
entityAuditsmember on the response.
{
"dsl": {
"from": 0,
"size": 10,
"query": {
"bool": {
// (1)
}
},
"sort": [
{
"created": {
"order": "desc"
}
}
],
"track_total_hits": true // (2)
}
}
- Replace the contents of the
boolportion of the query with the appropriate snippet from the earlier steps. - You must set
track_total_hitstotrueif you want an exact count of the number of results (in particular for pagination).
Review details of each activity
Each EntityAudit entry contains details of what occurred during an activity.
Contextual details
To access contextual details about the activity:
- Java
- Python
- Kotlin
- Raw REST API
for (EntityAudit result : results)
}
if (detail instanceof AtlanTag)
}
if (detail instanceof CustomMetadataAttributesAuditDetail)
}
- You can safely type-check the detailed object. You could generically use
Assethere instead ofTable, but if you know the type of asset you've requested the history for then the detailed object should be the same detailed type. - Once you've type-checked it, you can then coerce it.
- From there you can access any properties. Note that only properties actually set by the activity will have values in this detail object. So in this example, only if the description was actually changed to a new value would the
descriptionvariable now have any content. - This also means that if a field was actually removed (or cleared) by an activity you won't be able to distinguish that by just attempting to retrieve it. (It will be
nullwhether it was removed by the activity or simply wasn't changed by the activity.) To distinguish what was actually removed by an activity, you need to usegetNullFields(). The set returned by this method will contain the names of any fields that were actually removed (cleared) by the activity. - You can then take whatever action you like if a field was removed (cleared) by checking for its existence within the
getNullFields()set. - You can type-check the detailed object to see if it's an Atlan tag.
- Once you've type-checked it, you can then coerce it.
- You can access the Atlan tag name using
getTypeName(). - You can then compare this human-readable Atlan tag name to your expectations to take whatever action you like.
- You can type-check the detailed object to see if it details changes to custom metadata.
- Once you've type-checked it, you can then coerce it.
- You can access the name of the custom metadata using
getTypeName(). - You can retrieve which custom metadata attributes were changed using
getAttributes(). Since the result is a map, it will only contain attributes that were changed. If an attribute was removed (cleared) it will have a null value in the map but the name of the attribute will still be a key in the map. If a custom metadata attribute wasn't changed by the activity, it won't be a key in this map. - You can then compare these human-readable names to your expectations to take whatever action you like.
if isinstance(detail, Table): # (1)
description = detail.description # (2)
... # (3)
if isinstance(detail, AtlanTag): # (4)
class_name = detail.type_name # (5)
if class_name == "PII": # (6)
...
if isinstance(detail, CustomMetadataAttributesAuditDetail): # (7)
cm_name = detail.type_name # (8)
attributes = detail.attributes # (9)
if cm_name == "RACI" and attributes["Responsible"] == "jsmith": # (10)
...
- You can safely type-check the detailed object. You could generically use
Assethere instead ofTable, but if you know the type of asset you've requested the history for then the detailed object should be the same detailed type. - From there you can access any properties. Note that only properties actually set by the activity will have values in this detail object. So in this example, only if the description was actually changed to a new value would the
descriptionvariable now have any content. - You can then take whatever action you like
- You can type-check the detailed object to see if it's a 'AtlanTag'.
- You can access the 'AtlanTag' name using `type_name' property.
- You can then compare this human-readable Atlan tag name to your expectations to take whatever action you like.
- You can type-check the detailed object to see if it details changes to custom metadata.
- You can access the name of the custom metadata using then
type_nameattribute. - You can retrieve which custom metadata attributes were changed using the
attributespropery. Since the result is a dict, it will only contain attributes that were changed. If an attribute was removed (cleared) it will have a null value in the dict but the name of the attribute will still be a key in the map. If a custom metadata attribute wasn't changed by the activity, it won't be a key in this map. - You can then compare these human-readable names to your expectations to take whatever action you like.
if (detail is Table)
}
if (detail is AtlanTag)
}
if (detail is CustomMetadataAttributesAuditDetail)
}
- You can safely type-check the detailed object. You could generically use
Assethere instead ofTable, but if you know the type of asset you've requested the history for then the detailed object should be the same detailed type. - From there you can access any properties. Note that only properties actually set by the activity will have values in this detail object. So in this example, only if the description was actually changed to a new value would the
descriptionvariable now have any content. - This also means that if a field was actually removed (or cleared) by an activity you won't be able to distinguish that by just attempting to retrieve it. (It will be
nullwhether it was removed by the activity or simply wasn't changed by the activity.) To distinguish what was actually removed by an activity, you need to use.nullFields. The set returned by this method will contain the names of any fields that were actually removed (cleared) by the activity. - You can then take whatever action you like if a field was removed (cleared) by checking for its existence within the
.nullFieldsset. - You can type-check the detailed object to see if it's an Atlan tag.
- You can access the Atlan tag name using
.typeName. - You can then compare this human-readable Atlan tag name to your expectations to take whatever action you like.
- You can type-check the detailed object to see if it details changes to custom metadata.
- You can access the name of the custom metadata using
.typeName. - You can retrieve which custom metadata attributes were changed using
.attributes. Since the result is a map, it will only contain attributes that were changed. If an attribute was removed (cleared) it will have a null value in the map but the name of the attribute will still be a key in the map. If a custom metadata attribute wasn't changed by the activity, it won't be a key in this map. - You can then compare these human-readable names to your expectations to take whatever action you like.
The key point to note is that the format of the object within the detail of each record will vary, depending on the type of activity that occurred. You will therefore need to implement your own logic for detecting and inferring what kind of details are included when retrieving these from a raw API response.