|
3 | 3 | import csv |
4 | 4 | import datetime |
5 | 5 | import io |
6 | | -from collections.abc import Sequence |
| 6 | +from collections.abc import Mapping, Sequence |
7 | 7 | from dataclasses import dataclass |
8 | 8 | from enum import Enum, unique |
9 | | -from typing import Any, Self |
| 9 | +from typing import Any, Self, TypeIs |
10 | 10 |
|
11 | 11 | from beartype import BeartypeConf, beartype |
| 12 | +from beartype.door import TypeHint |
| 13 | + |
| 14 | + |
| 15 | +def _checked[T](value: object, hint: type[T], /) -> T: |
| 16 | + """Return a value after checking its runtime type.""" |
| 17 | + if not _is_type(value, hint): |
| 18 | + msg = f"Expected {hint!r}, got {value!r}." |
| 19 | + raise TypeError(msg) |
| 20 | + return value |
| 21 | + |
| 22 | + |
| 23 | +def _is_type[T](value: object, hint: type[T], /) -> TypeIs[T]: |
| 24 | + """Return whether a value satisfies a runtime type.""" |
| 25 | + return TypeHint(hint=hint).is_bearable(obj=value) |
| 26 | + |
| 27 | + |
| 28 | +def _number(value: object, /) -> int | float: |
| 29 | + """Return a runtime-validated JSON number.""" |
| 30 | + if isinstance(value, bool) or not isinstance(value, int | float): |
| 31 | + msg = f"Expected a number, got {value!r}." |
| 32 | + raise TypeError(msg) |
| 33 | + return value |
| 34 | + |
| 35 | + |
| 36 | +def _optional_string(value: object, /) -> str | None: |
| 37 | + """Return a runtime-validated optional string.""" |
| 38 | + if value is not None and not isinstance(value, str): |
| 39 | + msg = f"Expected an optional string, got {value!r}." |
| 40 | + raise TypeError(msg) |
| 41 | + return value |
12 | 42 |
|
13 | 43 |
|
14 | 44 | @beartype |
@@ -143,23 +173,29 @@ class QueryResult: |
143 | 173 | target_data: TargetData | None |
144 | 174 |
|
145 | 175 | @classmethod |
146 | | - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # pyrefly: ignore [explicit-any] |
| 176 | + def from_response_dict( |
| 177 | + cls, |
| 178 | + response_dict: Mapping[str, object], |
| 179 | + ) -> Self: |
147 | 180 | """Construct from a VWS API query result item dict.""" |
148 | 181 | target_data: TargetData | None = None |
149 | 182 | if "target_data" in response_dict: |
150 | | - target_data_dict = response_dict["target_data"] |
| 183 | + target_data_dict = _checked( |
| 184 | + response_dict["target_data"], dict[str, object] |
| 185 | + ) |
151 | 186 | target_timestamp = datetime.datetime.fromtimestamp( |
152 | | - timestamp=target_data_dict["target_timestamp"], # pyrefly: ignore [unknown-argument-type] |
| 187 | + timestamp=_number(target_data_dict["target_timestamp"]), |
153 | 188 | tz=datetime.UTC, |
154 | 189 | ) |
155 | 190 | target_data = TargetData( |
156 | | - name=target_data_dict["name"], # pyrefly: ignore [unknown-argument-type] |
157 | | - # pyrefly: ignore [unknown-argument-type] |
158 | | - application_metadata=target_data_dict["application_metadata"], |
| 191 | + name=_checked(target_data_dict["name"], str), |
| 192 | + application_metadata=_optional_string( |
| 193 | + target_data_dict["application_metadata"] |
| 194 | + ), |
159 | 195 | target_timestamp=target_timestamp, |
160 | 196 | ) |
161 | 197 | return cls( |
162 | | - target_id=response_dict["target_id"], |
| 198 | + target_id=_checked(response_dict["target_id"], str), |
163 | 199 | target_data=target_data, |
164 | 200 | ) |
165 | 201 |
|
@@ -301,48 +337,59 @@ class ModelTargetDatasetStatusReport: |
301 | 337 | """ |
302 | 338 |
|
303 | 339 | @classmethod |
304 | | - def from_response_dict(cls, response_dict: dict[str, Any]) -> Self: # pyrefly: ignore [explicit-any] |
| 340 | + def from_response_dict( |
| 341 | + cls, |
| 342 | + response_dict: Mapping[str, object], |
| 343 | + ) -> Self: |
305 | 344 | """Construct from a Model Target Web API response dict.""" |
306 | 345 | error: ModelTargetGenerationError | None = None |
307 | 346 | if "error" in response_dict: |
308 | | - error_dict = dict(response_dict["error"]) |
| 347 | + error_dict = _checked(response_dict["error"], dict[str, object]) |
309 | 348 | error = ModelTargetGenerationError( |
310 | | - code=error_dict["code"], |
311 | | - message=error_dict["message"], |
| 349 | + code=_checked(error_dict["code"], str), |
| 350 | + message=_checked(error_dict["message"], str), |
312 | 351 | ) |
313 | 352 |
|
314 | 353 | warning: ModelTargetGenerationWarning | None = None |
315 | 354 | if "warning" in response_dict: |
316 | | - warning_dict = dict(response_dict["warning"]) |
| 355 | + warning_dict = _checked( |
| 356 | + response_dict["warning"], dict[str, object] |
| 357 | + ) |
| 358 | + details = _checked( |
| 359 | + warning_dict["details"], list[dict[str, object]] |
| 360 | + ) |
317 | 361 | warning = ModelTargetGenerationWarning( |
318 | | - code=warning_dict["code"], |
319 | | - message=warning_dict["message"], |
320 | | - target=warning_dict["target"], |
| 362 | + code=_checked(warning_dict["code"], str), |
| 363 | + message=_checked(warning_dict["message"], str), |
| 364 | + target=_checked(warning_dict["target"], str), |
321 | 365 | details=[ |
322 | 366 | ModelTargetGenerationDetail( |
323 | | - code=detail["code"], # pyrefly: ignore [unknown-argument-type] |
324 | | - # pyrefly: ignore [unknown-argument-type] |
325 | | - message=detail["message"], |
| 367 | + code=_checked(detail["code"], str), |
| 368 | + message=_checked(detail["message"], str), |
326 | 369 | ) |
327 | | - for detail in warning_dict["details"] |
| 370 | + for detail in details |
328 | 371 | ], |
329 | 372 | ) |
330 | 373 |
|
331 | 374 | eta: datetime.datetime | None = None |
332 | 375 | if "eta" in response_dict: |
333 | | - eta = datetime.datetime.fromisoformat(response_dict["eta"]) |
| 376 | + eta = datetime.datetime.fromisoformat( |
| 377 | + _checked(response_dict["eta"], str), |
| 378 | + ) |
334 | 379 |
|
335 | 380 | completed_at: datetime.datetime | None = None |
336 | 381 | if "completedAt" in response_dict: |
337 | 382 | completed_at = datetime.datetime.fromisoformat( |
338 | | - response_dict["completedAt"], |
| 383 | + _checked(response_dict["completedAt"], str), |
339 | 384 | ) |
340 | 385 |
|
341 | 386 | return cls( |
342 | | - status=ModelTargetDatasetStatuses(value=response_dict["status"]), |
343 | | - dataset_uuid=response_dict["uuid"], |
| 387 | + status=ModelTargetDatasetStatuses( |
| 388 | + value=_checked(response_dict["status"], str), |
| 389 | + ), |
| 390 | + dataset_uuid=_checked(response_dict["uuid"], str), |
344 | 391 | created_at=datetime.datetime.fromisoformat( |
345 | | - response_dict["createdAt"], |
| 392 | + _checked(response_dict["createdAt"], str), |
346 | 393 | ), |
347 | 394 | eta=eta, |
348 | 395 | completed_at=completed_at, |
|
0 commit comments