Skip to content

Commit 023d3c0

Browse files
committed
[fix] Fix v9 integration test failures across multiple test files
- Fix AtlanTagName deserialization: change classification_names from list[str] to list to accept AtlanTagName objects from response translator - Fix lineage type_name preservation: update _to_related_catalog in Process/ColumnProcess to pass type_name through - Fix lineage immediate_upstream/downstream: type as list[LineageRef] instead of list[Any] - Add data_contract_latest/data_contract_latest_certified fields to Asset model - Add delete_handler field to Entity base class - Add Folder.creator() and Query.creator()/with_raw_query() factory methods - Add tags/terms fields to DCColumn contract model - Fix DataProduct.get_assets() to use msgspec.convert instead of **unpacking for IndexSearchRequest - Fix EntityAudit.detail conversion: add __post_init__ to convert nested entity dicts to Asset objects - Fix PopularityInsights timestamp type to accept date - Fix nested entity dict conversion in from_atlas_format for relationship attributes - Update data_mesh_test and lineage_test assertions for v9 UNSET semantics Made-with: Cursor
1 parent 56fd247 commit 023d3c0

132 files changed

Lines changed: 2242 additions & 2719 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pyatlan_v9/client/aio/asset.py

Lines changed: 105 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
SaveSemantic,
9191
)
9292
from pyatlan_v9.model.aggregation import Aggregations
93+
from pyatlan_v9.model.aio.core import AsyncAtlanRequest
9394
from pyatlan_v9.model.core import (
9495
Announcement,
9596
AssetRequest,
@@ -307,6 +308,15 @@ def _make_bulk_request_payload(entities: list, client) -> dict:
307308
return retranslated.translated
308309

309310

311+
async def _make_bulk_request_payload_async(entities: list, client) -> dict:
312+
"""Async version: serialize entities into API-ready dict with tag retranslation."""
313+
bulk = BulkRequest(entities=entities)
314+
request_dict = bulk.to_dict()
315+
async_request = AsyncAtlanRequest(instance=request_dict, client=client)
316+
await async_request.retranslate()
317+
return async_request.translated
318+
319+
310320
def _make_asset_request_payload(asset: Asset, client) -> dict:
311321
"""Serialize a single Asset entity into an API-ready dict,
312322
applying AtlanTag retranslation.
@@ -316,6 +326,14 @@ def _make_asset_request_payload(asset: Asset, client) -> dict:
316326
return retranslated.translated
317327

318328

329+
async def _make_asset_request_payload_async(asset: Asset, client) -> dict:
330+
"""Async version: serialize a single Asset entity into API-ready dict."""
331+
asset_dict = {"entity": json.loads(asset.to_json(nested=True))}
332+
async_request = AsyncAtlanRequest(instance=asset_dict, client=client)
333+
await async_request.retranslate()
334+
return async_request.translated
335+
336+
319337
# ---------------------------------------------------------------------------
320338
# V9 Async Asset Client (standalone)
321339
# ---------------------------------------------------------------------------
@@ -401,6 +419,44 @@ async def get_lineage_list(
401419
# Find by name helpers
402420
# ------------------------------------------------------------------
403421

422+
def _prepare_fluent_search(
423+
self,
424+
wheres: List[Query],
425+
attributes: Optional[List[str]] = None,
426+
related_attributes: Optional[List[str]] = None,
427+
):
428+
from pyatlan_v9.model.fluent_search import FluentSearch
429+
430+
search = FluentSearch()
431+
for w in wheres:
432+
search = search.where(w)
433+
for attr in attributes or []:
434+
search = search.include_on_results(attr)
435+
for rel_attr in related_attributes or []:
436+
search = search.include_on_relations(rel_attr)
437+
return search
438+
439+
def _build_find_request(
440+
self,
441+
name: str,
442+
type_name: str,
443+
attributes: Optional[List[str]] = None,
444+
) -> IndexSearchRequest:
445+
from pyatlan.model.search import Term
446+
from pyatlan_v9.model.search import DSL as V9DSL
447+
448+
if attributes is None:
449+
attributes = []
450+
query = (
451+
Term.with_state("ACTIVE")
452+
+ Term.with_type_name(type_name)
453+
+ Term.with_name(name)
454+
)
455+
dsl = V9DSL(query=query)
456+
return IndexSearchRequest(
457+
dsl=dsl, attributes=attributes, relation_attributes=["name"]
458+
)
459+
404460
@validate_arguments
405461
async def find_personas_by_name(
406462
self,
@@ -415,7 +471,7 @@ async def find_personas_by_name(
415471
:returns: all personas with that name, if found
416472
:raises NotFoundError: if no persona with the provided name exists
417473
"""
418-
search_request = FindPersonasByName.prepare_request(name, attributes)
474+
search_request = self._build_find_request(name, "PERSONA", attributes)
419475
search_results = await self.search(search_request)
420476
return FindPersonasByName.process_response(
421477
search_results, name, allow_multiple=True
@@ -435,7 +491,7 @@ async def find_purposes_by_name(
435491
:returns: all purposes with that name, if found
436492
:raises NotFoundError: if no purpose with the provided name exists
437493
"""
438-
search_request = FindPurposesByName.prepare_request(name, attributes)
494+
search_request = self._build_find_request(name, "PURPOSE", attributes)
439495
search_results = await self.search(search_request)
440496
return FindPurposesByName.process_response(
441497
search_results, name, allow_multiple=True
@@ -476,11 +532,13 @@ async def get_by_qualified_name(
476532
if (normalized_attributes and len(normalized_attributes)) or (
477533
normalized_related_attributes and len(normalized_related_attributes)
478534
):
479-
search = GetByQualifiedName.prepare_fluent_search_request(
480-
qualified_name,
481-
asset_type,
482-
normalized_attributes,
483-
normalized_related_attributes,
535+
search = self._prepare_fluent_search(
536+
wheres=[
537+
Asset.QUALIFIED_NAME.eq(qualified_name),
538+
Asset.TYPE_NAME.eq(asset_type.__name__),
539+
],
540+
attributes=normalized_attributes,
541+
related_attributes=normalized_related_attributes,
484542
)
485543
results = await search.execute_async(client=self._client)
486544
if results and results.current_page():
@@ -533,8 +591,13 @@ async def get_by_guid(
533591
if (normalized_attributes and len(normalized_attributes)) or (
534592
normalized_related_attributes and len(normalized_related_attributes)
535593
):
536-
search = GetByGuid.prepare_fluent_search_request(
537-
guid, asset_type, normalized_attributes, normalized_related_attributes
594+
search = self._prepare_fluent_search(
595+
wheres=[
596+
Asset.GUID.eq(guid),
597+
Asset.TYPE_NAME.eq(asset_type.__name__),
598+
],
599+
attributes=normalized_attributes,
600+
related_attributes=normalized_related_attributes,
538601
)
539602
results = await search.execute_async(client=self._client)
540603
if results and results.current_page():
@@ -638,8 +701,10 @@ async def save(
638701
asset.validate_required()
639702
await asset.flush_custom_metadata_async(client=self._client)
640703

641-
request = BulkRequest(entities=entities)
642-
raw_json = await self._client._call_api(BULK_UPDATE, query_params, request)
704+
request_payload = await _make_bulk_request_payload_async(
705+
entities, self._client
706+
)
707+
raw_json = await self._client._call_api(BULK_UPDATE, query_params, request_payload)
643708
response = _parse_mutation_response(raw_json)
644709

645710
if connections_created := response.assets_created(Connection):
@@ -766,8 +831,10 @@ async def save_replacing_cm(
766831
asset.validate_required()
767832
await asset.flush_custom_metadata_async(self._client)
768833

769-
request = BulkRequest(entities=entities)
770-
raw_json = await self._client._call_api(BULK_UPDATE, query_params, request)
834+
request_payload = await _make_bulk_request_payload_async(
835+
entities, self._client
836+
)
837+
raw_json = await self._client._call_api(BULK_UPDATE, query_params, request_payload)
771838
return _parse_mutation_response(raw_json)
772839

773840
@validate_arguments
@@ -910,8 +977,10 @@ async def _restore_asset(self, asset: Asset) -> AssetMutationResponse:
910977
for restored in entities:
911978
await restored.flush_custom_metadata_async(self._client)
912979

913-
request = BulkRequest(entities=entities)
914-
raw_json = await self._client._call_api(BULK_UPDATE, query_params, request)
980+
request_payload = await _make_bulk_request_payload_async(
981+
entities, self._client
982+
)
983+
raw_json = await self._client._call_api(BULK_UPDATE, query_params, request_payload)
915984
return _parse_mutation_response(raw_json)
916985

917986
# ------------------------------------------------------------------
@@ -1436,7 +1505,14 @@ async def _search_for_asset_with_name(
14361505
attributes: Optional[List],
14371506
allow_multiple: bool = False,
14381507
) -> List[A]:
1439-
search_request = SearchForAssetWithName.build_search_request(query, attributes)
1508+
from pyatlan_v9.model.search import DSL as V9DSL
1509+
1510+
dsl = V9DSL(query=query)
1511+
search_request = IndexSearchRequest(
1512+
dsl=dsl,
1513+
attributes=attributes or [],
1514+
relation_attributes=["name"],
1515+
)
14401516
results = await self.search(search_request)
14411517
return await SearchForAssetWithName.process_async_search_results(
14421518
results, name, asset_type, allow_multiple
@@ -1450,17 +1526,27 @@ async def _manage_terms(
14501526
guid: Optional[str] = None,
14511527
qualified_name: Optional[str] = None,
14521528
) -> A:
1529+
from pyatlan_v9.model.fluent_search import FluentSearch
1530+
14531531
ManageTerms.validate_guid_and_qualified_name(guid, qualified_name)
14541532

14551533
if guid:
1456-
search_query = ManageTerms.build_fluent_search_by_guid(asset_type, guid)
1534+
search_query = (
1535+
FluentSearch()
1536+
.select()
1537+
.where(Asset.TYPE_NAME.eq(asset_type.__name__))
1538+
.where(asset_type.GUID.eq(guid))
1539+
)
14571540
else:
14581541
if qualified_name is None:
14591542
raise ValueError(
14601543
"qualified_name cannot be None when guid is not provided"
14611544
)
1462-
search_query = ManageTerms.build_fluent_search_by_qualified_name(
1463-
asset_type, qualified_name
1545+
search_query = (
1546+
FluentSearch()
1547+
.select()
1548+
.where(Asset.TYPE_NAME.eq(asset_type.__name__))
1549+
.where(asset_type.QUALIFIED_NAME.eq(qualified_name))
14641550
)
14651551

14661552
results = await search_query.execute_async(client=self._client)

pyatlan_v9/client/aio/atlan.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ async def _create_params(
372372
if request_obj is not None:
373373
if api.consumes == APPLICATION_ENCODED_FORM:
374374
params["data"] = request_obj
375+
elif hasattr(request_obj, "to_dict") and callable(request_obj.to_dict):
376+
params["data"] = json.dumps(request_obj.to_dict())
375377
elif isinstance(request_obj, (msgspec.Struct, dict, list)):
376378
async_request = AsyncAtlanRequest(
377379
instance=request_obj, client=self # type: ignore[arg-type]
@@ -382,6 +384,8 @@ async def _create_params(
382384
instance=request_obj, client=self # type: ignore[arg-type]
383385
)
384386
params["data"] = await async_request.json()
387+
elif hasattr(request_obj, "__root__"):
388+
params["data"] = json.dumps(request_obj.__root__)
385389
else:
386390
params["data"] = json.dumps(request_obj)
387391
return params
@@ -746,6 +750,17 @@ async def upload_image(self, file, filename: str) -> AtlanImage:
746750
raw_json = await self._upload_file(UPLOAD_IMAGE, file=file, filename=filename)
747751
return msgspec.convert(raw_json, AtlanImage, strict=False)
748752

753+
async def search(self, criteria):
754+
"""Search assets. Delegates to asset.search()."""
755+
from warnings import warn
756+
757+
warn(
758+
"This method is deprecated, please use 'asset.search' instead, which offers identical functionality.",
759+
DeprecationWarning,
760+
stacklevel=2,
761+
)
762+
return await self.asset.search(criteria=criteria)
763+
749764
async def parse_query(self, query: QueryParserRequest) -> Optional[ParsedQuery]:
750765
"""
751766
Parses the provided query to describe its component parts.

0 commit comments

Comments
 (0)