Skip to content

Commit 7c977bf

Browse files
committed
feat: Enhance API responses with trace ID
1 parent 1087d04 commit 7c977bf

15 files changed

Lines changed: 760 additions & 118 deletions

‎dataconnect/client.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ def __init__(self, service: DataConnectService) -> None:
3434
"""Initialize the client with an injected service implementation."""
3535
self._service = service
3636

37+
@property
38+
def trace_id(self) -> str | None:
39+
"""Trace ID from the most recent sequential request, when available."""
40+
return getattr(self._service, "trace_id", None)
41+
3742
@classmethod
3843
def connect(
3944
cls,
@@ -67,7 +72,7 @@ def fetch_data(
6772
dataset_uuid: UUID,
6873
first_n_rows: int | None = None,
6974
) -> pd.DataFrame:
70-
"""Fetch data frames for a given dataset UUID."""
75+
"""Fetch data for a given dataset UUID."""
7176
return self._service.fetch_data(dataset_uuid, first_n_rows)
7277

7378
def get_datasets(
@@ -121,8 +126,8 @@ def dry_publish(
121126
122127
Returns:
123128
A :class:`DryPublishResult` containing the server's validation
124-
outcome, including per-field validity flags, error messages, and
125-
an optional ``invalid_records`` DataFrame.
129+
outcome, including per-field validity flags, error messages, an
130+
optional ``invalid_records`` DataFrame.
126131
"""
127132
return self._service.dry_publish(
128133
project_token=project_token,

‎dataconnect/exceptions.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class DataConnectError(Exception):
4848
message: str
4949
timestamp: str | None = None
5050
details: list[ErrorDetail] | None = None
51+
trace_id: str | None = None
5152

5253
def __str__(self) -> str:
5354
lines = [
@@ -63,6 +64,10 @@ def __str__(self) -> str:
6364
for detail in self.details:
6465
lines.append(str(detail))
6566

67+
if self.trace_id is not None:
68+
indent = " " if self.details else ""
69+
lines.append(f"{indent}Trace ID: {self.trace_id}")
70+
6671
return "\n".join(lines)
6772

6873

‎dataconnect/service/default.py‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ class DefaultDataConnectService(DataConnectService):
7474
def __init__(self, transport: Transport) -> None:
7575
self._transport = transport
7676

77+
@property
78+
def trace_id(self) -> str | None:
79+
return getattr(self._transport, "trace_id", None)
80+
7781
# DataConnectService
7882

7983
def get_studies(self, search_study_name: str | None = None) -> StudiesResult:
@@ -88,11 +92,13 @@ def get_studies(self, search_study_name: str | None = None) -> StudiesResult:
8892
- ``studies``: list of :class:`Study` objects matching the criteria.
8993
"""
9094

95+
self._transport.begin_request()
9196
request = ResourceQuery(action=_ACTION_LIST_STUDIES)
9297
request = request.append_body({"search_study_name": search_study_name})
9398

9499
try:
95-
resources = self._transport.list_resources(request)
100+
result = self._transport.list_resources(request)
101+
resources = result
96102
total_records = resources[0].total_records if resources else 0
97103
studies = [resource_to_study(r) for r in resources]
98104
return StudiesResult(total_records=total_records, studies=studies)
@@ -106,29 +112,32 @@ def get_dataset_versions(self, dataset_uuid: UUID) -> list[DatasetVersion]:
106112
dataset_uuid: UUID of the dataset whose versions are requested.
107113
108114
Returns:
109-
A list of :class:`DatasetVersion` objects for the given dataset.
115+
The dataset versions, newest first.
110116
111117
Raises:
112118
ValidationError: If *dataset_uuid* is not a valid UUID (upstream).
113119
"""
114120

121+
self._transport.begin_request()
115122
request = ResourceQuery(action=_ACTION_LIST_DATASET_VERSIONS).append_body({"dataset_uuid": str(dataset_uuid)})
116123

117124
try:
118-
resources = self._transport.list_resources(request)
125+
result = self._transport.list_resources(request)
119126

120127
# Return Sorted dataset versions in descending order (newest first) based on the dataset_version field.
121-
return sorted(
122-
(resource_to_dataset_version(r) for r in resources),
128+
versions = sorted(
129+
(resource_to_dataset_version(r) for r in result),
123130
key=lambda dv: dv.dataset_version,
124131
reverse=True,
125132
)
133+
return versions
126134
except Exception as ex:
127135
raise translate_error(ex) from ex
128136

129137
def fetch_data(self, dataset_uuid: UUID, first_n_rows: int | None = None) -> pd.DataFrame:
130138
"""Fetch data for a dataset"""
131139

140+
self._transport.begin_request()
132141
ticket = DatasetTicket(
133142
dataset_uuid=str(dataset_uuid),
134143
limit=_coerce_int_field("first_n_rows", first_n_rows),
@@ -159,6 +168,7 @@ def get_datasets(
159168
A :class:`PaginatedResponse` of :class:`Dataset` items matching the criteria.
160169
"""
161170

171+
self._transport.begin_request()
162172
request = ResourceQuery(action=_ACTION_LIST_DATASETS).append_body(
163173
{
164174
"study_environment_uuid": str(study_environment_uuid),
@@ -169,7 +179,8 @@ def get_datasets(
169179
)
170180

171181
try:
172-
resources = self._transport.list_resources(request)
182+
result = self._transport.list_resources(request)
183+
resources = result
173184
items = []
174185
for resource in resources:
175186
dataset = resource_to_dataset(resource)
@@ -229,6 +240,7 @@ def dry_publish(
229240
for example :class:`ValidationError`,
230241
:class:`AuthorizationError`, or :class:`ServerError`."""
231242

243+
self._transport.begin_request()
232244
request = PublishRequest(
233245
input_config=json.dumps(
234246
{
@@ -291,6 +303,7 @@ def publish(
291303
layer is translated by :func:`translate_error` into the public
292304
API's :class:`DataConnectError` hierarchy before propagating.
293305
"""
306+
self._transport.begin_request()
294307
request = PublishRequest(
295308
input_config=json.dumps(
296309
{
@@ -344,10 +357,11 @@ def get_datetime_formats(
344357
layer is translated by :func:`translate_error` into the public
345358
API's :class:`DataConnectError` hierarchy.
346359
"""
360+
self._transport.begin_request()
347361
request = DatetimeFormatsRequest(project_token=project_token, format_type=format_type)
348362

349363
try:
350-
raw_formats = self._transport.get_datetime_formats(request)
364+
formats_response = self._transport.get_datetime_formats(request)
351365
except TransportError as ex:
352366
raise translate_error(ex) from ex
353367

@@ -356,7 +370,7 @@ def get_datetime_formats(
356370
format=fmt,
357371
type="datetime" if "HH:mm" in fmt else "date",
358372
)
359-
for fmt in raw_formats
373+
for fmt in formats_response
360374
]
361375

362376
return DatetimeFormatsResult(formats=formats)

‎dataconnect/service/error_handler.py‎

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,54 @@ def translate_error(ex: Exception) -> DataConnectError:
4343

4444
if isinstance(ex, TransportAuthenticationError):
4545
return AuthenticationError(
46-
error_code=ex.error_code, message=ex.message, timestamp=ex.timestamp, details=error_details
46+
error_code=ex.error_code,
47+
message=ex.message,
48+
timestamp=ex.timestamp,
49+
details=error_details,
50+
trace_id=ex.trace_id,
4751
)
4852

4953
if isinstance(ex, TransportAuthorizationError):
5054
return AuthorizationError(
51-
error_code=ex.error_code, message=ex.message, timestamp=ex.timestamp, details=error_details
55+
error_code=ex.error_code,
56+
message=ex.message,
57+
timestamp=ex.timestamp,
58+
details=error_details,
59+
trace_id=ex.trace_id,
5260
)
5361

5462
if isinstance(ex, TransportValidationError):
5563
return ValidationError(
56-
error_code=ex.error_code, message=ex.message, timestamp=ex.timestamp, details=error_details
64+
error_code=ex.error_code,
65+
message=ex.message,
66+
timestamp=ex.timestamp,
67+
details=error_details,
68+
trace_id=ex.trace_id,
5769
)
5870

5971
if isinstance(ex, TransportNotFoundError):
6072
return NotFoundError(
61-
error_code=ex.error_code, message=ex.message, timestamp=ex.timestamp, details=error_details
73+
error_code=ex.error_code,
74+
message=ex.message,
75+
timestamp=ex.timestamp,
76+
details=error_details,
77+
trace_id=ex.trace_id,
6278
)
6379

6480
if isinstance(ex, TransportServerError):
65-
return ServerError(error_code=ex.error_code, message=ex.message, timestamp=ex.timestamp, details=error_details)
81+
return ServerError(
82+
error_code=ex.error_code,
83+
message=ex.message,
84+
timestamp=ex.timestamp,
85+
details=error_details,
86+
trace_id=ex.trace_id,
87+
)
6688

6789
# Non-specific transport error
68-
return DataConnectError(error_code=ex.error_code, message=ex.message, timestamp=ex.timestamp, details=error_details)
90+
return DataConnectError(
91+
error_code=ex.error_code,
92+
message=ex.message,
93+
timestamp=ex.timestamp,
94+
details=error_details,
95+
trace_id=ex.trace_id,
96+
)

0 commit comments

Comments
 (0)