Skip to content

Commit badc0e6

Browse files
GOV-667: Fix CI and minor bugs
1 parent 4c42e9a commit badc0e6

5 files changed

Lines changed: 47 additions & 19 deletions

File tree

pyatlan/client/aio/client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ def __init__(self, **kwargs):
168168

169169
# Create async session with custom transport that supports retry and proxy
170170
self._async_session = httpx.AsyncClient(
171-
transport=PyatlanAsyncTransport(retry=self.retry, client=self, **transport_kwargs),
171+
transport=PyatlanAsyncTransport(
172+
retry=self.retry, client=self, **transport_kwargs
173+
),
172174
headers={
173175
"x-atlan-agent": "sdk",
174176
"x-atlan-agent-id": "python",
@@ -980,7 +982,9 @@ async def max_retries( # type: ignore[override,misc]
980982
if self.verify is not None:
981983
transport_kwargs["verify"] = self.verify
982984

983-
new_transport = PyatlanAsyncTransport(retry=max_retries, client=self, **transport_kwargs)
985+
new_transport = PyatlanAsyncTransport(
986+
retry=max_retries, client=self, **transport_kwargs
987+
)
984988
session._transport = new_transport
985989

986990
LOGGER.debug(

pyatlan/client/common/transport.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Provides duplicate AuthPolicy detection logic used by both
77
PyatlanSyncTransport and PyatlanAsyncTransport.
88
"""
9+
910
from __future__ import annotations
1011

1112
import json
@@ -21,7 +22,9 @@
2122
logger = logging.getLogger(__name__)
2223

2324

24-
def build_policy_search_request(policy_name: str, persona_guid: str) -> IndexSearchRequest:
25+
def build_policy_search_request(
26+
policy_name: str, persona_guid: str
27+
) -> IndexSearchRequest:
2528
"""Build an IndexSearchRequest to find an existing AuthPolicy by name and persona."""
2629
query = Bool(
2730
filter=[
@@ -36,7 +39,9 @@ def build_policy_search_request(policy_name: str, persona_guid: str) -> IndexSea
3639
)
3740

3841

39-
def create_mock_response(existing_policy: dict, temp_guid: str = "-1") -> httpx.Response:
42+
def create_mock_response(
43+
existing_policy: dict, temp_guid: str = "-1"
44+
) -> httpx.Response:
4045
"""Build a mock bulk-entity response containing an already-created policy."""
4146
response_body = {
4247
"mutatedEntities": {"CREATE": [existing_policy]},
@@ -59,7 +64,14 @@ def parse_auth_policy_entity(request: httpx.Request) -> Optional[tuple[str, str,
5964
if not request.content:
6065
return None
6166

62-
body = json.loads(request.content.decode("utf-8"))
67+
try:
68+
body = json.loads(request.content.decode("utf-8"))
69+
except (json.JSONDecodeError, UnicodeDecodeError):
70+
logger.debug(
71+
"parse_auth_policy_entity: failed to decode request body, skipping duplicate check"
72+
)
73+
return None
74+
6375
for entity in body.get("entities", []):
6476
if entity.get("typeName") != "AuthPolicy":
6577
continue
@@ -73,7 +85,9 @@ def parse_auth_policy_entity(request: httpx.Request) -> Optional[tuple[str, str,
7385
return None
7486

7587

76-
def find_existing_policy(client: Any, policy_name: str, persona_guid: str) -> Optional[dict]:
88+
def find_existing_policy(
89+
client: Any, policy_name: str, persona_guid: str
90+
) -> Optional[dict]:
7791
"""
7892
Search for an existing AuthPolicy by name and persona GUID (synchronous).
7993
@@ -89,7 +103,7 @@ def find_existing_policy(client: Any, policy_name: str, persona_guid: str) -> Op
89103
except Exception as e:
90104
raise ErrorCode.UNABLE_TO_SEARCH_EXISTING_POLICY.exception_with_parameters(
91105
policy_name, persona_guid, str(e)
92-
)
106+
) from e
93107

94108

95109
async def find_existing_policy_async(
@@ -110,7 +124,7 @@ async def find_existing_policy_async(
110124
except Exception as e:
111125
raise ErrorCode.UNABLE_TO_SEARCH_EXISTING_POLICY.exception_with_parameters(
112126
policy_name, persona_guid, str(e)
113-
)
127+
) from e
114128

115129

116130
def check_for_duplicate_policy(
@@ -159,7 +173,9 @@ async def check_for_duplicate_policy_async(
159173
return None
160174

161175
policy_name, persona_guid, temp_guid = parsed
162-
existing_policy = await find_existing_policy_async(client, policy_name, persona_guid)
176+
existing_policy = await find_existing_policy_async(
177+
client, policy_name, persona_guid
178+
)
163179
if existing_policy:
164180
logger.info(
165181
f"Found existing policy '{policy_name}' with guid "

pyatlan/client/transport.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# type: ignore
21
"""
32
Custom HTTP transport with retry support for Atlan Python SDK.
43
@@ -131,6 +130,7 @@ def _retry_operation(
131130
response = e
132131
continue
133132

133+
assert isinstance(response, httpx.Response)
134134
if retry.is_exhausted() or not retry.is_retryable_status_code(
135135
response.status_code
136136
):
@@ -250,6 +250,7 @@ async def _retry_operation_async(
250250
response = e
251251
continue
252252

253+
assert isinstance(response, httpx.Response)
253254
if retry.is_exhausted() or not retry.is_retryable_status_code(
254255
response.status_code
255256
):

tests/integration/transport_test.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
IndexSearch duplicate-check runs against the real server, validating
1010
the full transport logic.
1111
"""
12+
1213
from unittest.mock import patch
1314

1415
import httpx
@@ -102,7 +103,7 @@ def intercepting_handle(request: httpx.Request) -> httpx.Response:
102103
return _build_fake_bulk_response(policy_name, persona.guid)
103104
return original_inner_handle(request)
104105

105-
transport._transport.handle_request = intercepting_handle
106+
transport._transport.handle_request = intercepting_handle # type: ignore[method-assign]
106107

107108
try:
108109
policy = Persona.create_metadata_policy(
@@ -123,7 +124,7 @@ def intercepting_handle(request: httpx.Request) -> httpx.Response:
123124
)
124125
finally:
125126
client._session._transport = original_transport
126-
transport._transport.handle_request = original_inner_handle
127+
transport._transport.handle_request = original_inner_handle # type: ignore[method-assign]
127128

128129

129130
def test_duplicate_prevention_short_circuits_when_policy_exists(
@@ -173,7 +174,7 @@ def intercepting_handle(request: httpx.Request) -> httpx.Response:
173174
return _build_fake_bulk_response(policy_name, persona.guid)
174175
return original_inner_handle(request)
175176

176-
transport._transport.handle_request = intercepting_handle
177+
transport._transport.handle_request = intercepting_handle # type: ignore[method-assign]
177178

178179
try:
179180
# Patch find_existing_policy in the common transport module so the
@@ -204,4 +205,4 @@ def intercepting_handle(request: httpx.Request) -> httpx.Response:
204205
)
205206
finally:
206207
client._session._transport = original_transport
207-
transport._transport.handle_request = original_inner_handle
208+
transport._transport.handle_request = original_inner_handle # type: ignore[method-assign]

tests/unit/test_transport.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# Copyright 2025 Atlan Pte. Ltd.
33
"""Unit tests for pyatlan.client.transport and pyatlan.client.common.transport."""
4-
from unittest.mock import AsyncMock, MagicMock, patch
4+
5+
from unittest.mock import AsyncMock, MagicMock
56

67
import httpx
78
import pytest
@@ -17,7 +18,6 @@
1718
)
1819
from pyatlan.client.constants import BULK_UPDATE
1920
from pyatlan.client.transport import PyatlanAsyncTransport, PyatlanSyncTransport
20-
from pyatlan.errors import ErrorCode
2121

2222

2323
# ---------------------------------------------------------------------------
@@ -111,6 +111,14 @@ def test_uses_custom_temp_guid(self):
111111
policy_name, persona_guid, temp_guid = parse_auth_policy_entity(req)
112112
assert temp_guid == "-3"
113113

114+
def test_returns_none_for_invalid_json(self):
115+
req = httpx.Request("POST", BULK_URL, content=b"{not valid json")
116+
assert parse_auth_policy_entity(req) is None
117+
118+
def test_returns_none_for_invalid_utf8(self):
119+
req = httpx.Request("POST", BULK_URL, content=b"\xff\xfe")
120+
assert parse_auth_policy_entity(req) is None
121+
114122

115123
# ---------------------------------------------------------------------------
116124
# create_mock_response
@@ -400,9 +408,7 @@ def _make_transport(self, client=None):
400408
@pytest.mark.asyncio
401409
async def test_duplicate_prevention_short_circuits_retry(self):
402410
mock_client = MagicMock()
403-
mock_client._call_api = AsyncMock(
404-
return_value={"entities": [EXISTING_POLICY]}
405-
)
411+
mock_client._call_api = AsyncMock(return_value={"entities": [EXISTING_POLICY]})
406412
transport = self._make_transport(client=mock_client)
407413

408414
call_count = 0

0 commit comments

Comments
 (0)