volcano-sdk-python 0.13.1__py3-none-any.whl → 0.13.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,8 @@ from dataclasses import dataclass
15
15
  from typing import TYPE_CHECKING
16
16
  from urllib.parse import urlsplit
17
17
 
18
+ import httpx
19
+
18
20
  if TYPE_CHECKING:
19
21
  from .errors import NotFoundError
20
22
 
@@ -106,28 +108,30 @@ def valid_invoke_url(value: object, api_url: str) -> str | None:
106
108
  return None
107
109
 
108
110
 
109
- def _absolute_url_scheme(value: str) -> str:
110
- """Return the lowercase scheme of an absolute URL, or "" when there is none.
111
+ def _absolute_url_scheme(value: str) -> str | None:
112
+ """Return the lowercase scheme of an absolute URL, or None when unusable.
111
113
 
112
- Unparseable input yields "" rather than raising, so a malformed URL reads
114
+ Unparseable input yields None rather than raising, so a malformed URL reads
113
115
  as unusable to every caller.
114
116
 
115
117
  Returns
116
118
  -------
117
- str
118
- The lowercase scheme, or an empty string for an invalid authority or URL.
119
+ str or None
120
+ The lowercase scheme, or None for an invalid authority or URL.
119
121
 
120
122
  """
121
123
  if not value or any(character.isspace() for character in value):
122
- return ""
124
+ return None
123
125
  try:
124
126
  parsed = urlsplit(value)
125
127
  # Reading the authority is the validation: an unclosed IPv6 literal or
126
128
  # a port out of range raises here rather than at request time.
127
129
  host, _port = parsed.hostname, parsed.port
128
- except ValueError:
129
- return ""
130
- return parsed.scheme.lower() if host else ""
130
+ # urlsplit accepts controls that HTTPX rejects when invoking the URL.
131
+ _ = httpx.URL(value)
132
+ except (ValueError, httpx.InvalidURL):
133
+ return None
134
+ return parsed.scheme.lower() if host else None
131
135
 
132
136
 
133
137
  def lookup(api_url: str, authorization: str, name: str) -> CachedOutcome | None:
@@ -2,8 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import math
5
6
  from collections.abc import Mapping
6
- from typing import TYPE_CHECKING, cast
7
+ from typing import TYPE_CHECKING, TypeGuard
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from .models import JSONValue
@@ -11,6 +12,38 @@ if TYPE_CHECKING:
11
12
  INVALID_LOG_RESPONSE = "Expected a complete log response"
12
13
 
13
14
 
15
+ def _is_object_mapping(value: object) -> TypeGuard[Mapping[object, object]]:
16
+ return isinstance(value, Mapping)
17
+
18
+
19
+ def _is_object_list(value: object) -> TypeGuard[list[object]]:
20
+ return isinstance(value, list)
21
+
22
+
23
+ def _is_object_tuple(value: object) -> TypeGuard[tuple[object, ...]]:
24
+ return isinstance(value, tuple)
25
+
26
+
27
+ def _is_json_scalar(value: object) -> TypeGuard[str | int | float | bool | None]:
28
+ if isinstance(value, float):
29
+ return math.isfinite(value)
30
+ return value is None or isinstance(value, (str, int, bool))
31
+
32
+
33
+ def _is_json_value(value: object) -> TypeGuard[JSONValue]:
34
+ if _is_json_scalar(value):
35
+ return True
36
+ if _is_object_list(value):
37
+ return all(_is_json_value(item) for item in value)
38
+ if _is_object_tuple(value):
39
+ return all(_is_json_value(item) for item in value)
40
+ if _is_object_mapping(value):
41
+ return all(
42
+ isinstance(key, str) and _is_json_value(item) for key, item in value.items()
43
+ )
44
+ return False
45
+
46
+
14
47
  def response_values(payload: object) -> Mapping[str, object]:
15
48
  """Require a JSON object for the response envelope.
16
49
 
@@ -21,9 +54,25 @@ def response_values(payload: object) -> Mapping[str, object]:
21
54
  TypeError: The response envelope is not an object.
22
55
 
23
56
  """
24
- if not isinstance(payload, Mapping):
57
+ if not _is_object_mapping(payload):
25
58
  raise TypeError(INVALID_LOG_RESPONSE)
26
- return cast("Mapping[str, object]", payload)
59
+ values: dict[str, object] = {}
60
+ for key, value in payload.items():
61
+ if not isinstance(key, str):
62
+ raise TypeError(INVALID_LOG_RESPONSE)
63
+ values[key] = value
64
+ return values
65
+
66
+
67
+ def _row_values(item: object) -> Mapping[str, JSONValue]:
68
+ if not _is_object_mapping(item):
69
+ raise TypeError(INVALID_LOG_RESPONSE)
70
+ row: dict[str, JSONValue] = {}
71
+ for key, value in item.items():
72
+ if not isinstance(key, str) or not _is_json_value(value):
73
+ raise TypeError(INVALID_LOG_RESPONSE)
74
+ row[key] = value
75
+ return row
27
76
 
28
77
 
29
78
  def response_data(values: Mapping[str, object]) -> tuple[Mapping[str, JSONValue], ...]:
@@ -37,12 +86,9 @@ def response_data(values: Mapping[str, object]) -> tuple[Mapping[str, JSONValue]
37
86
 
38
87
  """
39
88
  raw_data = values.get("data")
40
- if not isinstance(raw_data, list):
41
- raise TypeError(INVALID_LOG_RESPONSE)
42
- data = cast("list[object]", raw_data)
43
- if any(not isinstance(item, Mapping) for item in data):
89
+ if not _is_object_list(raw_data):
44
90
  raise TypeError(INVALID_LOG_RESPONSE)
45
- return tuple(cast("Mapping[str, JSONValue]", item) for item in data)
91
+ return tuple(_row_values(item) for item in raw_data)
46
92
 
47
93
 
48
94
  def search_metadata(values: Mapping[str, object]) -> tuple[int, bool, str | None]: