66with httpx's HTTPTransport while respecting proxy and SSL configurations.
77"""
88
9- import json
109import logging
1110from functools import partial
1211from typing import TYPE_CHECKING , Any , Optional , Union
1312
1413import httpx
1514from httpx_retries import Retry
1615
16+ from pyatlan .client .common .transport import (
17+ check_for_duplicate_policy ,
18+ check_for_duplicate_policy_async ,
19+ )
20+
1721if TYPE_CHECKING :
1822 from pyatlan .client .atlan import AtlanClient
1923
2024logger = logging .getLogger (__name__ )
2125
22- def _find_existing_policy (
23- client : "AtlanClient" , policy_name : str , persona_guid : str
24- ) -> Optional [dict ]:
25- """Search for an existing AuthPolicy by name and persona GUID."""
26- try :
27- from pyatlan .client .constants import INDEX_SEARCH
28- from pyatlan .model .search import Bool , DSL , IndexSearchRequest , Term
29-
30- query = Bool (
31- filter = [
32- Term (field = "__typeName.keyword" , value = "AuthPolicy" ),
33- Term (field = "name.keyword" , value = policy_name ),
34- Term (field = "__persona" , value = persona_guid ),
35- ]
36- )
37- search_request = IndexSearchRequest (
38- dsl = DSL (query = query , size = 1 , from_ = 0 ),
39- attributes = ["name" , "qualifiedName" ],
40- )
41- raw_json = client ._call_api (INDEX_SEARCH , request_obj = search_request )
42- if raw_json and raw_json .get ("entities" ):
43- return raw_json ["entities" ][0 ]
44- return None
45- except Exception as e :
46- logger .debug (f"Error searching for existing policy: { e } " )
47- return None
48-
49-
50- def _create_mock_response (
51- existing_policy : dict , temp_guid : str = "-1"
52- ) -> httpx .Response :
53- """Build a mock bulk-entity response containing an already-created policy."""
54- response_body = {
55- "mutatedEntities" : {"CREATE" : [existing_policy ]},
56- "guidAssignments" : {temp_guid : existing_policy .get ("guid" )},
57- }
58- return httpx .Response (
59- status_code = 200 ,
60- json = response_body ,
61- request = httpx .Request ("POST" , "http://mock" ),
62- )
63-
64-
65- async def _find_existing_policy_async (
66- client : Any , policy_name : str , persona_guid : str
67- ) -> Optional [dict ]:
68- """Async version of _find_existing_policy for use with AsyncAtlanClient."""
69- try :
70- from pyatlan .client .constants import INDEX_SEARCH
71- from pyatlan .model .search import Bool , DSL , IndexSearchRequest , Term
72-
73- query = Bool (
74- filter = [
75- Term (field = "__typeName.keyword" , value = "AuthPolicy" ),
76- Term (field = "name.keyword" , value = policy_name ),
77- Term (field = "__persona" , value = persona_guid ),
78- ]
79- )
80- search_request = IndexSearchRequest (
81- dsl = DSL (query = query , size = 1 , from_ = 0 ),
82- attributes = ["name" , "qualifiedName" ],
83- )
84- raw_json = await client ._call_api (INDEX_SEARCH , request_obj = search_request )
85- if raw_json and raw_json .get ("entities" ):
86- return raw_json ["entities" ][0 ]
87- return None
88- except Exception as e :
89- logger .debug (f"Error searching for existing policy (async): { e } " )
90- return None
91-
92-
93- async def _check_for_duplicate_policy_async (
94- client : Any , request : httpx .Request
95- ) -> Optional [httpx .Response ]:
96- """Async version of _check_for_duplicate_policy for use with AsyncAtlanClient."""
97- try :
98- if request .method != "POST" or "/api/meta/entity/bulk" not in str (request .url ):
99- return None
100- if not request .content :
101- return None
102-
103- body = json .loads (request .content .decode ("utf-8" ))
104- for entity in body .get ("entities" , []):
105- if entity .get ("typeName" ) != "AuthPolicy" :
106- continue
107- policy_name = entity .get ("attributes" , {}).get ("name" )
108- access_control = entity .get ("attributes" , {}).get ("accessControl" )
109- persona_guid = (
110- access_control .get ("guid" )
111- if isinstance (access_control , dict )
112- else None
113- )
114- if not (policy_name and persona_guid ):
115- continue
116- existing_policy = await _find_existing_policy_async (
117- client , policy_name , persona_guid
118- )
119- if existing_policy :
120- logger .info (
121- f"Found existing policy '{ policy_name } ' with guid "
122- f"{ existing_policy .get ('guid' )} during retry check"
123- )
124- return _create_mock_response (existing_policy , entity .get ("guid" , "-1" ))
125- return None
126- except Exception as e :
127- logger .debug (f"Duplicate policy check failed (will proceed with retry): { e } " )
128- return None
129-
130-
131- def _check_for_duplicate_policy (
132- client : "AtlanClient" , request : httpx .Request
133- ) -> Optional [httpx .Response ]:
134- """
135- Check whether a bulk POST is creating an AuthPolicy that already exists.
136- Only called during retry attempts, never on the first request.
137-
138- Returns a mock response with the existing policy if a duplicate is found,
139- or None to let the retry proceed normally.
140- """
141- try :
142- if request .method != "POST" or "/api/meta/entity/bulk" not in str (request .url ):
143- return None
144- if not request .content :
145- return None
146-
147- body = json .loads (request .content .decode ("utf-8" ))
148- for entity in body .get ("entities" , []):
149- if entity .get ("typeName" ) != "AuthPolicy" :
150- continue
151- policy_name = entity .get ("attributes" , {}).get ("name" )
152- access_control = entity .get ("attributes" , {}).get ("accessControl" )
153- persona_guid = (
154- access_control .get ("guid" )
155- if isinstance (access_control , dict )
156- else None
157- )
158- if not (policy_name and persona_guid ):
159- continue
160- existing_policy = _find_existing_policy (client , policy_name , persona_guid )
161- if existing_policy :
162- logger .info (
163- f"Found existing policy '{ policy_name } ' with guid "
164- f"{ existing_policy .get ('guid' )} during retry check"
165- )
166- return _create_mock_response (existing_policy , entity .get ("guid" , "-1" ))
167- return None
168- except Exception as e :
169- logger .debug (f"Duplicate policy check failed (will proceed with retry): { e } " )
170- return None
171-
17226
17327class PyatlanSyncTransport (httpx .BaseTransport ):
17428 """
@@ -228,11 +82,8 @@ def handle_request(self, request: httpx.Request) -> httpx.Response:
22882 """
22983 logger .debug ("handle_request started request=%s" , request )
23084
231- if self .retry .is_retryable_method (request .method ):
232- send_method = partial (self ._transport .handle_request )
233- response = self ._retry_operation (request , send_method )
234- else :
235- response = self ._transport .handle_request (request )
85+ send_method = partial (self ._transport .handle_request )
86+ response = self ._retry_operation (request , send_method )
23687
23788 logger .debug (
23889 "handle_request finished request=%s response=%s" , request , response
@@ -256,7 +107,9 @@ def _retry_operation(
256107
257108 # ONLY during retry: check if this is a policy creation and if duplicate exists
258109 if self ._client :
259- duplicate_response = _check_for_duplicate_policy (self ._client , request )
110+ duplicate_response = check_for_duplicate_policy (
111+ self ._client , request
112+ )
260113 if duplicate_response :
261114 logger .warning (
262115 "RETRY PREVENTED: Policy already exists (likely from previous "
@@ -343,11 +196,8 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
343196 """
344197 logger .debug ("handle_async_request started request=%s" , request )
345198
346- if self .retry .is_retryable_method (request .method ):
347- send_method = partial (self ._transport .handle_async_request )
348- response = await self ._retry_operation_async (request , send_method )
349- else :
350- response = await self ._transport .handle_async_request (request )
199+ send_method = partial (self ._transport .handle_async_request )
200+ response = await self ._retry_operation_async (request , send_method )
351201
352202 logger .debug (
353203 "handle_async_request finished request=%s response=%s" , request , response
@@ -373,7 +223,7 @@ async def _retry_operation_async(
373223
374224 # ONLY during retry: check if this is a policy creation and if duplicate exists
375225 if self ._client :
376- duplicate_response = await _check_for_duplicate_policy_async (
226+ duplicate_response = await check_for_duplicate_policy_async (
377227 self ._client , request
378228 )
379229 if duplicate_response :
0 commit comments