instance-repo 1.4.7__tar.gz → 1.4.8.dev0__tar.gz
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.
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/PKG-INFO +1 -1
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/__init__.py +1 -1
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/config.py +7 -4
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/instances.py +342 -61
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/models.py +1 -1
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/store/oss_discovery.py +35 -15
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/store/registry.py +9 -5
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo.egg-info/PKG-INFO +1 -1
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/pyproject.toml +1 -1
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/README.md +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/_routing.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/_site_defaults.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/artifacts.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/bootstrap.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/cache.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/cli.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/__init__.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/_rfc3339.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/ap_tool.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/benchmarks.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/datasets.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/images.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/reports.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/scaffold.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/shared.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/splits.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/user_data_acl.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/user_data_directories.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/user_data_entries.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/user_data_reads.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/user_data_upload.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/user_data_writes.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/versions.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/workflows.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/concurrency.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/content.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/endpoints.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/errors.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/image.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/layout.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/loader.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/paths.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/release.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/repo.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/retry.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/store/__init__.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/store/acr.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/store/base.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/store/oss.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/tasktoml.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/transport.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/validate.py +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo.egg-info/SOURCES.txt +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo.egg-info/dependency_links.txt +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo.egg-info/entry_points.txt +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo.egg-info/requires.txt +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo.egg-info/top_level.txt +0 -0
- {instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/setup.cfg +0 -0
|
@@ -151,18 +151,21 @@ def normalize_storage_env(storage_env: str | None) -> str:
|
|
|
151
151
|
return (storage_env or "").strip().lower()
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
def business_metadata_model(metadata_model: str | None
|
|
155
|
-
|
|
154
|
+
def business_metadata_model(metadata_model: str | None, *,
|
|
155
|
+
allow_version_first: bool = False) -> str:
|
|
156
|
+
"""Normalize a business model, optionally allowing explicit legacy reads.
|
|
156
157
|
|
|
157
158
|
``None``, the empty compatibility value, and ``split_first`` are accepted.
|
|
158
|
-
``version_first`` remains
|
|
159
|
-
|
|
159
|
+
``version_first`` remains rejected by normal business I/O unless the caller
|
|
160
|
+
explicitly opts into legacy metadata addressing.
|
|
160
161
|
"""
|
|
161
162
|
from ..errors import SchemaError
|
|
162
163
|
|
|
163
164
|
if metadata_model is not None and not isinstance(metadata_model, str):
|
|
164
165
|
raise SchemaError("metadata_model must be a string or None")
|
|
165
166
|
requested = (metadata_model or "").strip()
|
|
167
|
+
if allow_version_first and requested == "version_first":
|
|
168
|
+
return requested
|
|
166
169
|
if requested not in ("", "split_first"):
|
|
167
170
|
if requested == "version_first":
|
|
168
171
|
raise SchemaError(
|
|
@@ -32,17 +32,169 @@ logger = logging.getLogger(__name__)
|
|
|
32
32
|
|
|
33
33
|
def _registry_overrides(environment: str | None,
|
|
34
34
|
metadata_model: str | None,
|
|
35
|
-
profile_model: str | None = None
|
|
35
|
+
profile_model: str | None = None, *,
|
|
36
|
+
allow_version_first: bool = False) -> dict:
|
|
36
37
|
"""Build explicit split-first registry parameters before any registry I/O."""
|
|
37
38
|
overrides: dict = {
|
|
38
39
|
"metadata_model": business_metadata_model(
|
|
39
|
-
metadata_model if metadata_model is not None else profile_model
|
|
40
|
+
metadata_model if metadata_model is not None else profile_model,
|
|
41
|
+
allow_version_first=allow_version_first),
|
|
40
42
|
}
|
|
41
43
|
if environment is not None:
|
|
42
44
|
overrides["environment"] = environment
|
|
43
45
|
return overrides
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
_MAX_TAG_FILTER_KEYS = 64
|
|
49
|
+
_MAX_TAG_FILTER_VALUES = 64
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _validate_tag_key(key: str) -> str:
|
|
53
|
+
if not isinstance(key, str):
|
|
54
|
+
raise SchemaError("tag keys must be strings")
|
|
55
|
+
key = key.strip()
|
|
56
|
+
if not key:
|
|
57
|
+
raise SchemaError("tag key must not be empty or whitespace-only")
|
|
58
|
+
if "." in key or "$" in key:
|
|
59
|
+
raise SchemaError(
|
|
60
|
+
f"invalid tag key {key!r}: keys must not contain '.' or "
|
|
61
|
+
f"'$' (server rejects them)")
|
|
62
|
+
return key
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _copy_tags(tags: dict[str, str | list[str]], *,
|
|
66
|
+
allow_empty: bool = False) -> dict[str, str | list[str]]:
|
|
67
|
+
if not isinstance(tags, dict):
|
|
68
|
+
raise SchemaError("tags must be an object of string or string-array values")
|
|
69
|
+
copied: dict[str, str | list[str]] = {}
|
|
70
|
+
for raw_key, value in tags.items():
|
|
71
|
+
key = _validate_tag_key(raw_key)
|
|
72
|
+
if key in copied:
|
|
73
|
+
raise SchemaError(f"duplicate tag key {key!r} after normalization")
|
|
74
|
+
if isinstance(value, str):
|
|
75
|
+
value = value.strip()
|
|
76
|
+
if not value and not allow_empty:
|
|
77
|
+
raise SchemaError(
|
|
78
|
+
f"invalid value for tag {key!r}: scalar must be non-empty")
|
|
79
|
+
copied[key] = value
|
|
80
|
+
elif isinstance(value, list) and all(isinstance(item, str) for item in value):
|
|
81
|
+
copied[key] = _normalize_tag_values(value, allow_empty=allow_empty)
|
|
82
|
+
else:
|
|
83
|
+
raise SchemaError(
|
|
84
|
+
f"invalid value for tag {key!r}: expected a string or a list of strings")
|
|
85
|
+
return copied
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _effective_tags(tags: dict[str, str | list[str]]) -> dict[str, str | list[str]]:
|
|
89
|
+
"""Project tags through the server's write normalization for no-op detection."""
|
|
90
|
+
normalized = _copy_tags(tags, allow_empty=True)
|
|
91
|
+
return {key: value for key, value in normalized.items()
|
|
92
|
+
if not isinstance(value, list) or value}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _clean_legacy_tags(
|
|
96
|
+
tags: dict[str, str | list[str]]) -> dict[str, str | list[str]]:
|
|
97
|
+
"""Remove legacy empty scalars and array items from an internal PATCH map."""
|
|
98
|
+
normalized = _copy_tags(tags, allow_empty=True)
|
|
99
|
+
cleaned: dict[str, str | list[str]] = {}
|
|
100
|
+
for key, value in normalized.items():
|
|
101
|
+
if isinstance(value, str):
|
|
102
|
+
if value:
|
|
103
|
+
cleaned[key] = value
|
|
104
|
+
else:
|
|
105
|
+
cleaned[key] = [item for item in value if item]
|
|
106
|
+
return cleaned
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _normalize_tag_values(values: list[str], *,
|
|
110
|
+
allow_empty: bool = False) -> list[str]:
|
|
111
|
+
normalized: list[str] = []
|
|
112
|
+
seen: set[str] = set()
|
|
113
|
+
for value in values:
|
|
114
|
+
value = value.strip()
|
|
115
|
+
if (value or allow_empty) and value not in seen:
|
|
116
|
+
seen.add(value)
|
|
117
|
+
normalized.append(value)
|
|
118
|
+
return normalized
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _parse_tag_filters_csv(tags: str) -> dict[str, str]:
|
|
122
|
+
"""Parse legacy CSV using the server's skip/trim/duplicate-key rules."""
|
|
123
|
+
parsed: dict[str, str] = {}
|
|
124
|
+
for pair in tags.split(","):
|
|
125
|
+
key, separator, value = pair.partition(":")
|
|
126
|
+
key, value = key.strip(), value.strip()
|
|
127
|
+
if not separator or not key or not value:
|
|
128
|
+
continue
|
|
129
|
+
if key in parsed:
|
|
130
|
+
raise SchemaError(f"duplicate tag filter key {key!r}")
|
|
131
|
+
parsed[key] = value
|
|
132
|
+
return _normalize_tag_filters(parsed)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _normalize_tag_filters(
|
|
136
|
+
tags: dict[str, str | dict[str, list[str]]] | str | None) -> dict:
|
|
137
|
+
"""Return the single normalized filter representation used by both paths."""
|
|
138
|
+
if tags is None:
|
|
139
|
+
return {}
|
|
140
|
+
if isinstance(tags, str):
|
|
141
|
+
return _parse_tag_filters_csv(tags)
|
|
142
|
+
if not isinstance(tags, dict):
|
|
143
|
+
raise SchemaError("tags must be a dict, legacy CSV string, or None")
|
|
144
|
+
if len(tags) > _MAX_TAG_FILTER_KEYS:
|
|
145
|
+
raise SchemaError(f"tag filters must contain at most {_MAX_TAG_FILTER_KEYS} keys")
|
|
146
|
+
normalized: dict = {}
|
|
147
|
+
for raw_key, condition in tags.items():
|
|
148
|
+
key = _validate_tag_key(raw_key)
|
|
149
|
+
if key in normalized:
|
|
150
|
+
raise SchemaError(f"duplicate tag filter key {key!r} after normalization")
|
|
151
|
+
if isinstance(condition, str):
|
|
152
|
+
value = condition.strip()
|
|
153
|
+
if not value:
|
|
154
|
+
raise SchemaError(f"tag filter {key!r} requires a non-empty value")
|
|
155
|
+
normalized[key] = value
|
|
156
|
+
continue
|
|
157
|
+
if not isinstance(condition, dict) or len(condition) != 1:
|
|
158
|
+
raise SchemaError(
|
|
159
|
+
f"invalid filter for tag {key!r}: expected a string or one any/all object")
|
|
160
|
+
operation, values = next(iter(condition.items()))
|
|
161
|
+
if (operation not in ("any", "all") or not isinstance(values, list)
|
|
162
|
+
or not all(isinstance(value, str) for value in values)):
|
|
163
|
+
raise SchemaError(
|
|
164
|
+
f"invalid filter for tag {key!r}: any/all requires a string array")
|
|
165
|
+
if len(values) > _MAX_TAG_FILTER_VALUES:
|
|
166
|
+
raise SchemaError(
|
|
167
|
+
f"tag filter {key!r} must contain at most "
|
|
168
|
+
f"{_MAX_TAG_FILTER_VALUES} raw values")
|
|
169
|
+
values = _normalize_tag_values(values)
|
|
170
|
+
if not values:
|
|
171
|
+
raise SchemaError(
|
|
172
|
+
f"invalid filter for tag {key!r}: any/all requires a nonempty string array")
|
|
173
|
+
normalized[key] = {operation: values}
|
|
174
|
+
return normalized
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _matches_tag_filters(instance_tags: dict, filters: dict) -> bool:
|
|
178
|
+
for key, condition in filters.items():
|
|
179
|
+
actual = instance_tags.get(key)
|
|
180
|
+
if isinstance(actual, str):
|
|
181
|
+
actual_values = [actual]
|
|
182
|
+
elif isinstance(actual, list):
|
|
183
|
+
actual_values = list(actual)
|
|
184
|
+
else:
|
|
185
|
+
actual_values = []
|
|
186
|
+
if isinstance(condition, str):
|
|
187
|
+
if condition not in actual_values:
|
|
188
|
+
return False
|
|
189
|
+
continue
|
|
190
|
+
operation, expected = next(iter(condition.items()))
|
|
191
|
+
if operation == "any" and not any(value in actual_values for value in expected):
|
|
192
|
+
return False
|
|
193
|
+
if operation == "all" and not all(value in actual_values for value in expected):
|
|
194
|
+
return False
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
|
|
46
198
|
def _validate_custom_asset_rel(rel: str) -> None:
|
|
47
199
|
"""白名单校验 custom 资产相对路径(防元数据被篡改造成 pull 路径穿越)。
|
|
48
200
|
|
|
@@ -225,7 +377,8 @@ class InstancesClient:
|
|
|
225
377
|
# 保持缺省调用形态与历史一致,避免破坏既有实现。
|
|
226
378
|
overrides = _registry_overrides(
|
|
227
379
|
environment, metadata_model,
|
|
228
|
-
getattr(self._p, "metadata_model", None)
|
|
380
|
+
getattr(self._p, "metadata_model", None),
|
|
381
|
+
allow_version_first=True)
|
|
229
382
|
try:
|
|
230
383
|
result = self._reg.get(
|
|
231
384
|
dataset, version, instance_id, split, **overrides)
|
|
@@ -271,7 +424,7 @@ class InstancesClient:
|
|
|
271
424
|
def list_paged(self, dataset: str, version: str = "", *,
|
|
272
425
|
split: str | None = None, q: str | None = None,
|
|
273
426
|
instance_id: str | None = None,
|
|
274
|
-
tags: dict[str, str] | str | None = None,
|
|
427
|
+
tags: dict[str, str | dict[str, list[str]]] | str | None = None,
|
|
275
428
|
difficulty: str | None = None,
|
|
276
429
|
page: int = 1, page_size: int = 50,
|
|
277
430
|
environment: str | None = None,
|
|
@@ -281,9 +434,10 @@ class InstancesClient:
|
|
|
281
434
|
|
|
282
435
|
与 :meth:`list` 的自动翻页取全量互补:精确控制页码/过滤/搜索。
|
|
283
436
|
参数逐项对齐服务端(无 SDK 发明项):``q`` 是 instance_id **子串**
|
|
284
|
-
模糊搜索(不区分大小写);``instance_id`` 精确等值;``tags`` dict
|
|
285
|
-
|
|
286
|
-
|
|
437
|
+
模糊搜索(不区分大小写);``instance_id`` 精确等值;``tags`` dict 编码为
|
|
438
|
+
JSON object(值为 string、``{"any": [...]}`` 或 ``{"all": [...]}``),
|
|
439
|
+
旧 CSV 字符串("k1:v1,k2:v2")保持兼容;key 含 ``.``/``$`` 前置拒绝。
|
|
440
|
+
``difficulty`` easy/medium/hard。**服务端无排序参数**
|
|
287
441
|
(固定 instance_id asc),SDK 不暴露。
|
|
288
442
|
|
|
289
443
|
**OSS 回退**:当 ``TransportError(biz_code=92001)`` 或 ``92023`` 时回退到 OSS 存储层,
|
|
@@ -296,20 +450,15 @@ class InstancesClient:
|
|
|
296
450
|
if (difficulty or "").strip() and difficulty.strip() not in ("easy", "medium", "hard"):
|
|
297
451
|
raise SchemaError(
|
|
298
452
|
f"invalid difficulty {difficulty!r}; must be easy/medium/hard")
|
|
299
|
-
#
|
|
453
|
+
# 控制面编码与 OSS 回退共享同一份归一化结果,避免两条路径语义漂移。
|
|
454
|
+
normalized_filters = _normalize_tag_filters(tags)
|
|
300
455
|
tags_q = ""
|
|
301
|
-
if tags:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
key = p.split(":", 1)[0]
|
|
308
|
-
if not key or "." in key or "$" in key:
|
|
309
|
-
raise SchemaError(
|
|
310
|
-
f"invalid tag key {key!r}: keys must not contain '.' or "
|
|
311
|
-
f"'$' (server rejects them)")
|
|
312
|
-
tags_q = ",".join(pairs)
|
|
456
|
+
if isinstance(tags, dict) and normalized_filters:
|
|
457
|
+
tags_q = json.dumps(
|
|
458
|
+
normalized_filters, ensure_ascii=False, separators=(",", ":"))
|
|
459
|
+
elif isinstance(tags, str) and normalized_filters:
|
|
460
|
+
tags_q = ",".join(f"{key}:{value}"
|
|
461
|
+
for key, value in normalized_filters.items())
|
|
313
462
|
params: list[tuple[str, str]] = [("dataset_name", dataset.strip())]
|
|
314
463
|
if (version or "").strip():
|
|
315
464
|
params.append(("version", version.strip()))
|
|
@@ -346,7 +495,8 @@ class InstancesClient:
|
|
|
346
495
|
return self._discover_instances_paged(
|
|
347
496
|
dataset, version, split,
|
|
348
497
|
q=q, instance_id=instance_id,
|
|
349
|
-
tags=
|
|
498
|
+
tags=normalized_filters,
|
|
499
|
+
difficulty=difficulty,
|
|
350
500
|
page=page, page_size=page_size)
|
|
351
501
|
raise
|
|
352
502
|
rows = data.get("instances", data) if isinstance(data, dict) else data
|
|
@@ -371,26 +521,30 @@ class InstancesClient:
|
|
|
371
521
|
return req == derived
|
|
372
522
|
|
|
373
523
|
def _discover_instances(self, dataset: str, version: str,
|
|
374
|
-
split: str | None = None
|
|
524
|
+
split: str | None = None, *,
|
|
525
|
+
read_content: bool = False) -> list[DatasetInstance]:
|
|
375
526
|
"""OSS 回退的 instances.list — 从 OSS 列举指定 version/split 下的实例。"""
|
|
376
527
|
from ..store.oss_discovery import build_synthetic_instances
|
|
377
528
|
return build_synthetic_instances(
|
|
378
529
|
self._content, dataset, version, split,
|
|
379
|
-
oss_prefix=getattr(self, '_oss_prefix', 'swe/datasets')
|
|
530
|
+
oss_prefix=getattr(self, '_oss_prefix', 'swe/datasets'),
|
|
531
|
+
read_content=read_content)
|
|
380
532
|
|
|
381
533
|
def _discover_instance(self, dataset: str, version: str,
|
|
382
534
|
instance_id: str,
|
|
383
|
-
split: str | None = None
|
|
535
|
+
split: str | None = None, *,
|
|
536
|
+
read_content: bool = False) -> DatasetInstance | None:
|
|
384
537
|
"""OSS 回退的 instances.get — 精确查找单个实例。"""
|
|
385
538
|
from ..store.oss_discovery import build_synthetic_instance
|
|
386
539
|
return build_synthetic_instance(
|
|
387
540
|
self._content, dataset, version, instance_id, split,
|
|
388
|
-
oss_prefix=getattr(self, '_oss_prefix', 'swe/datasets')
|
|
541
|
+
oss_prefix=getattr(self, '_oss_prefix', 'swe/datasets'),
|
|
542
|
+
read_content=read_content)
|
|
389
543
|
|
|
390
544
|
def _discover_instances_paged(
|
|
391
545
|
self, dataset: str, version: str, split: str | None, *,
|
|
392
546
|
q: str | None, instance_id: str | None,
|
|
393
|
-
tags: dict[str, str
|
|
547
|
+
tags: dict[str, str | dict[str, list[str]]],
|
|
394
548
|
difficulty: str | None,
|
|
395
549
|
page: int, page_size: int,
|
|
396
550
|
) -> "PagedResult":
|
|
@@ -400,47 +554,32 @@ class InstancesClient:
|
|
|
400
554
|
"""
|
|
401
555
|
from ..store.oss_discovery import _in_memory_paged
|
|
402
556
|
|
|
403
|
-
#
|
|
557
|
+
# list_paged 已归一化并共享同一份 filters,这里不再重复校验/归一化。
|
|
558
|
+
tags_dict = tags or {}
|
|
559
|
+
normalized_difficulty = (difficulty or "").strip()
|
|
560
|
+
# tags/difficulty 过滤需要 GetObject;普通过滤/列表保持 list-only 零新增读取。
|
|
561
|
+
read_content = bool(tags_dict) or bool(normalized_difficulty)
|
|
404
562
|
if instance_id:
|
|
405
|
-
inst = self._discover_instance(
|
|
563
|
+
inst = self._discover_instance(
|
|
564
|
+
dataset, version, instance_id, split, read_content=read_content)
|
|
406
565
|
items: list[DatasetInstance] = [inst] if inst else []
|
|
407
566
|
else:
|
|
408
|
-
items = self._discover_instances(
|
|
567
|
+
items = self._discover_instances(
|
|
568
|
+
dataset, version, split, read_content=read_content)
|
|
409
569
|
|
|
410
570
|
# 客户端过滤
|
|
411
571
|
if q:
|
|
412
572
|
q_lower = q.lower()
|
|
413
573
|
items = [i for i in items
|
|
414
574
|
if q_lower in i.instance_id.lower()]
|
|
415
|
-
if
|
|
416
|
-
items = [i for i in items
|
|
417
|
-
if i.difficulty == difficulty]
|
|
418
|
-
if tags:
|
|
419
|
-
tags_dict = (
|
|
420
|
-
tags if isinstance(tags, dict)
|
|
421
|
-
else self._parse_tags_csv(tags)
|
|
422
|
-
)
|
|
575
|
+
if normalized_difficulty:
|
|
423
576
|
items = [i for i in items
|
|
424
|
-
if
|
|
425
|
-
|
|
577
|
+
if i.difficulty == normalized_difficulty]
|
|
578
|
+
if tags_dict:
|
|
579
|
+
items = [i for i in items if _matches_tag_filters(i.tags, tags_dict)]
|
|
426
580
|
|
|
427
581
|
return _in_memory_paged(items, int(page), int(page_size))
|
|
428
582
|
|
|
429
|
-
@staticmethod
|
|
430
|
-
def _parse_tags_csv(tags_str: str) -> dict[str, str]:
|
|
431
|
-
"""解析 ``"k1:v1,k2:v2"`` 格式的 tags CSV 字符串为 dict。"""
|
|
432
|
-
result: dict[str, str] = {}
|
|
433
|
-
for pair in str(tags_str).split(","):
|
|
434
|
-
pair = pair.strip()
|
|
435
|
-
if not pair:
|
|
436
|
-
continue
|
|
437
|
-
parts = pair.split(":", 1)
|
|
438
|
-
if len(parts) == 2:
|
|
439
|
-
result[parts[0]] = parts[1]
|
|
440
|
-
else:
|
|
441
|
-
result[parts[0]] = ""
|
|
442
|
-
return result
|
|
443
|
-
|
|
444
583
|
# ── 实例目录/文件列举(GET /datasets/objects)──────────────────
|
|
445
584
|
|
|
446
585
|
def list_objects(self, dataset: str, instance_id: str,
|
|
@@ -703,14 +842,149 @@ class InstancesClient:
|
|
|
703
842
|
if failure is not None:
|
|
704
843
|
raise failure from None
|
|
705
844
|
|
|
845
|
+
def _read_tag_record(self, dataset: str, version: str, instance_id: str, *,
|
|
846
|
+
split: str | None, environment: str | None,
|
|
847
|
+
metadata_model: str | None) -> DatasetInstance:
|
|
848
|
+
record = self.get(
|
|
849
|
+
dataset, version, instance_id, split=split,
|
|
850
|
+
environment=environment, metadata_model=metadata_model,
|
|
851
|
+
fallback_to_oss=False)
|
|
852
|
+
if record is None:
|
|
853
|
+
raise SchemaError(f"instance {instance_id!r} not found")
|
|
854
|
+
return record
|
|
855
|
+
|
|
856
|
+
def _read_tags(self, dataset: str, version: str, instance_id: str, *,
|
|
857
|
+
split: str | None, environment: str | None,
|
|
858
|
+
metadata_model: str | None) -> dict[str, str | list[str]]:
|
|
859
|
+
record = self._read_tag_record(
|
|
860
|
+
dataset, version, instance_id, split=split, environment=environment,
|
|
861
|
+
metadata_model=metadata_model)
|
|
862
|
+
return _copy_tags(record.tags, allow_empty=True)
|
|
863
|
+
|
|
864
|
+
def list_tags(self, dataset: str, version: str, instance_id: str, *,
|
|
865
|
+
split: str | None = None, environment: str | None = None,
|
|
866
|
+
metadata_model: str | None = None) -> dict[str, str | list[str]]:
|
|
867
|
+
"""Read the complete tag map through the existing metadata/RBAC path."""
|
|
868
|
+
return self._read_tags(
|
|
869
|
+
dataset, version, instance_id, split=split, environment=environment,
|
|
870
|
+
metadata_model=metadata_model)
|
|
871
|
+
|
|
872
|
+
def set_tags(self, dataset: str, version: str, instance_id: str,
|
|
873
|
+
tags: dict[str, str | list[str]], *, split: str | None = None,
|
|
874
|
+
environment: str | None = None,
|
|
875
|
+
metadata_model: str | None = None) -> DatasetInstance:
|
|
876
|
+
"""Set keys in the complete tag map, preserving every unspecified key.
|
|
877
|
+
|
|
878
|
+
This is a non-atomic read-modify-write of the whole map. A concurrent
|
|
879
|
+
writer can be overwritten by this call's later whole-map update.
|
|
880
|
+
"""
|
|
881
|
+
normalized = _copy_tags(tags)
|
|
882
|
+
record = self._read_tag_record(
|
|
883
|
+
dataset, version, instance_id, split=split, environment=environment,
|
|
884
|
+
metadata_model=metadata_model)
|
|
885
|
+
current = _copy_tags(record.tags, allow_empty=True)
|
|
886
|
+
merged = dict(current)
|
|
887
|
+
merged.update(normalized)
|
|
888
|
+
if _effective_tags(merged) == _effective_tags(current):
|
|
889
|
+
return record
|
|
890
|
+
patch_tags = _clean_legacy_tags(merged)
|
|
891
|
+
return self.update(
|
|
892
|
+
dataset, version, instance_id, split=split, tags=patch_tags,
|
|
893
|
+
environment=environment, metadata_model=metadata_model)
|
|
894
|
+
|
|
895
|
+
def append_tags(self, dataset: str, version: str, instance_id: str,
|
|
896
|
+
tags: dict[str, str | list[str]], *, split: str | None = None,
|
|
897
|
+
environment: str | None = None,
|
|
898
|
+
metadata_model: str | None = None) -> DatasetInstance:
|
|
899
|
+
"""Append unique values, converting scalars only when a value is added."""
|
|
900
|
+
normalized = _copy_tags(tags)
|
|
901
|
+
record = self._read_tag_record(
|
|
902
|
+
dataset, version, instance_id, split=split, environment=environment,
|
|
903
|
+
metadata_model=metadata_model)
|
|
904
|
+
current_tags = _copy_tags(record.tags, allow_empty=True)
|
|
905
|
+
merged = dict(current_tags)
|
|
906
|
+
for key, added in normalized.items():
|
|
907
|
+
current = merged.get(key)
|
|
908
|
+
values = ([current] if isinstance(current, str)
|
|
909
|
+
else list(current) if isinstance(current, list) else [])
|
|
910
|
+
has_new_value = False
|
|
911
|
+
for value in [added] if isinstance(added, str) else added:
|
|
912
|
+
if value not in values:
|
|
913
|
+
values.append(value)
|
|
914
|
+
has_new_value = True
|
|
915
|
+
if has_new_value:
|
|
916
|
+
merged[key] = values
|
|
917
|
+
if _effective_tags(merged) == _effective_tags(current_tags):
|
|
918
|
+
return record
|
|
919
|
+
patch_tags = _clean_legacy_tags(merged)
|
|
920
|
+
return self.update(
|
|
921
|
+
dataset, version, instance_id, split=split, tags=patch_tags,
|
|
922
|
+
environment=environment, metadata_model=metadata_model)
|
|
923
|
+
|
|
924
|
+
def remove_tags(self, dataset: str, version: str, instance_id: str,
|
|
925
|
+
tags: dict[str, str | list[str]], *, split: str | None = None,
|
|
926
|
+
environment: str | None = None,
|
|
927
|
+
metadata_model: str | None = None) -> DatasetInstance:
|
|
928
|
+
"""Remove values by non-atomic whole-map replacement; arrays stay arrays."""
|
|
929
|
+
normalized = _copy_tags(tags, allow_empty=True)
|
|
930
|
+
record = self._read_tag_record(
|
|
931
|
+
dataset, version, instance_id, split=split, environment=environment,
|
|
932
|
+
metadata_model=metadata_model)
|
|
933
|
+
current_tags = _copy_tags(record.tags, allow_empty=True)
|
|
934
|
+
merged = dict(current_tags)
|
|
935
|
+
for key, removed in normalized.items():
|
|
936
|
+
if key not in merged:
|
|
937
|
+
continue
|
|
938
|
+
current = merged[key]
|
|
939
|
+
current_is_array = isinstance(current, list)
|
|
940
|
+
values = list(current) if current_is_array else [current]
|
|
941
|
+
removed_values = [removed] if isinstance(removed, str) else removed
|
|
942
|
+
remaining = [value for value in values if value not in removed_values]
|
|
943
|
+
if not remaining:
|
|
944
|
+
merged.pop(key)
|
|
945
|
+
else:
|
|
946
|
+
merged[key] = remaining if current_is_array else remaining[0]
|
|
947
|
+
if _effective_tags(merged) == _effective_tags(current_tags):
|
|
948
|
+
return record
|
|
949
|
+
patch_tags = _clean_legacy_tags(merged)
|
|
950
|
+
return self.update(
|
|
951
|
+
dataset, version, instance_id, split=split, tags=patch_tags,
|
|
952
|
+
environment=environment, metadata_model=metadata_model)
|
|
953
|
+
|
|
954
|
+
def clear_tags(self, dataset: str, version: str, instance_id: str,
|
|
955
|
+
keys: list[str], *, split: str | None = None,
|
|
956
|
+
environment: str | None = None,
|
|
957
|
+
metadata_model: str | None = None) -> DatasetInstance:
|
|
958
|
+
"""Delete complete keys using non-atomic whole-map replacement."""
|
|
959
|
+
if isinstance(keys, (str, bytes)):
|
|
960
|
+
raise SchemaError("clear_tags keys must be an iterable of strings")
|
|
961
|
+
try:
|
|
962
|
+
validated_keys = [_validate_tag_key(key) for key in keys]
|
|
963
|
+
except TypeError as exc:
|
|
964
|
+
raise SchemaError("clear_tags keys must be an iterable of strings") from exc
|
|
965
|
+
record = self._read_tag_record(
|
|
966
|
+
dataset, version, instance_id, split=split, environment=environment,
|
|
967
|
+
metadata_model=metadata_model)
|
|
968
|
+
current = _copy_tags(record.tags, allow_empty=True)
|
|
969
|
+
merged = dict(current)
|
|
970
|
+
for key in validated_keys:
|
|
971
|
+
merged.pop(key, None)
|
|
972
|
+
if _effective_tags(merged) == _effective_tags(current):
|
|
973
|
+
return record
|
|
974
|
+
patch_tags = _clean_legacy_tags(merged)
|
|
975
|
+
return self.update(
|
|
976
|
+
dataset, version, instance_id, split=split, tags=patch_tags,
|
|
977
|
+
environment=environment, metadata_model=metadata_model)
|
|
978
|
+
|
|
706
979
|
def update(self, dataset: str, version: str, instance_id: str, *,
|
|
707
980
|
split: str | None = None, dataset_split_id: str | None = None,
|
|
708
981
|
manifest: list[dict] | None = None,
|
|
709
|
-
tags: dict[str, str] | None = None,
|
|
982
|
+
tags: dict[str, str | list[str]] | None = None,
|
|
710
983
|
difficulty: str | None = None,
|
|
711
984
|
docker_image: str | None = None,
|
|
712
985
|
extend_meta: dict | None = None,
|
|
713
|
-
environment: str | None = None
|
|
986
|
+
environment: str | None = None,
|
|
987
|
+
metadata_model: str | None = None) -> DatasetInstance:
|
|
714
988
|
"""更新实例元数据(``PATCH /datasets/instances/detail``,不动 OSS 字节)。
|
|
715
989
|
|
|
716
990
|
服务端 body 白名单(严格解析,未知字段 400):``manifest``(整体替换,
|
|
@@ -720,14 +994,15 @@ class InstancesClient:
|
|
|
720
994
|
**至少一项必填**(SDK 前置校验)。
|
|
721
995
|
|
|
722
996
|
正常业务固定 split_first,使用 ``dataset_split_id`` 或
|
|
723
|
-
dataset+version+split+instance_id
|
|
997
|
+
dataset+version+split+instance_id 寻址。``metadata_model=None`` 保持
|
|
998
|
+
split_first;显式 ``version_first`` 可读取并更新历史布局记录。
|
|
724
999
|
"""
|
|
725
1000
|
from urllib.parse import quote
|
|
726
1001
|
body: dict = {}
|
|
727
1002
|
if manifest is not None:
|
|
728
1003
|
body["manifest"] = manifest
|
|
729
1004
|
if tags is not None:
|
|
730
|
-
body["tags"] = tags
|
|
1005
|
+
body["tags"] = _copy_tags(tags)
|
|
731
1006
|
if difficulty is not None:
|
|
732
1007
|
if difficulty not in ("", "easy", "medium", "hard"):
|
|
733
1008
|
raise SchemaError(
|
|
@@ -762,7 +1037,9 @@ class InstancesClient:
|
|
|
762
1037
|
env = (getattr(self._p, "dataset_environment", "") or "").strip()
|
|
763
1038
|
if env:
|
|
764
1039
|
q += f"&environment={quote(env, safe='')}"
|
|
765
|
-
|
|
1040
|
+
model = business_metadata_model(
|
|
1041
|
+
metadata_model, allow_version_first=True)
|
|
1042
|
+
q += f"&metadata_model={quote(model, safe='')}"
|
|
766
1043
|
data = self._t.patch(f"{self._BASE}/detail?{q}", body)
|
|
767
1044
|
return DatasetInstance.from_dict(data) if isinstance(data, dict) else DatasetInstance(
|
|
768
1045
|
instance_id=instance_id, dataset=dataset, dataset_version=version or "")
|
|
@@ -1949,9 +2226,13 @@ class InstancesClient:
|
|
|
1949
2226
|
def _tags(metadata: dict) -> dict:
|
|
1950
2227
|
t = metadata.get("tags", {})
|
|
1951
2228
|
if isinstance(t, dict):
|
|
1952
|
-
return
|
|
2229
|
+
return _copy_tags(t)
|
|
2230
|
+
# 与 DatasetInstance.from_dict 的旧读取口径一致:顶层 []string 不再猜测成 map。
|
|
1953
2231
|
if isinstance(t, list):
|
|
1954
|
-
|
|
2232
|
+
logger.warning(
|
|
2233
|
+
"legacy top-level tags array is ignored; use a tags map with "
|
|
2234
|
+
"string or string-array values instead")
|
|
2235
|
+
return {}
|
|
1955
2236
|
return {}
|
|
1956
2237
|
|
|
1957
2238
|
|
|
@@ -218,7 +218,7 @@ class DatasetInstance:
|
|
|
218
218
|
split: str = "default"
|
|
219
219
|
id: str | None = None
|
|
220
220
|
manifest: list[ManifestEntry] = field(default_factory=list)
|
|
221
|
-
tags: dict[str, str] = field(default_factory=dict)
|
|
221
|
+
tags: dict[str, str | list[str]] = field(default_factory=dict)
|
|
222
222
|
difficulty: str | None = None
|
|
223
223
|
docker_image: str | None = None
|
|
224
224
|
extend_meta: dict[str, str] = field(default_factory=dict)
|
|
@@ -361,12 +361,12 @@ def _warn_if_truncated(info: OssDatasetLevelInfo, dataset: str, max_versions: in
|
|
|
361
361
|
# JSON reading helper
|
|
362
362
|
# ---------------------------------------------------------------------------
|
|
363
363
|
|
|
364
|
-
def _read_json_object(content_store, key: str) -> dict:
|
|
365
|
-
"""
|
|
364
|
+
def _read_json_object(content_store, key: str, *, required: bool = False) -> dict:
|
|
365
|
+
"""Read a JSON object; ``required=True`` never hides read/parse failures.
|
|
366
366
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
Best-effort discovery retains the historical empty-object fallback. Tag-filtered
|
|
368
|
+
discovery uses ``required=True`` so missing GetObject permission, oversized data,
|
|
369
|
+
network errors and malformed JSON cannot be misreported as "no match".
|
|
370
370
|
"""
|
|
371
371
|
try:
|
|
372
372
|
# Use the internal _execute_oss to do a HEAD and check size.
|
|
@@ -375,6 +375,10 @@ def _read_json_object(content_store, key: str) -> dict:
|
|
|
375
375
|
head = bucket.head_object(key)
|
|
376
376
|
content_length = getattr(head, "content_length", 0)
|
|
377
377
|
if content_length and content_length > _MAX_JSON_BYTES:
|
|
378
|
+
if required:
|
|
379
|
+
raise TransportError(
|
|
380
|
+
f"required instance metadata JSON {key!r} exceeds "
|
|
381
|
+
f"{_MAX_JSON_BYTES} bytes (content_length={content_length})")
|
|
378
382
|
logger.warning(
|
|
379
383
|
"Skipping oversized JSON object %s (%d bytes)",
|
|
380
384
|
key, content_length,
|
|
@@ -389,15 +393,35 @@ def _read_json_object(content_store, key: str) -> dict:
|
|
|
389
393
|
return content_store._execute_oss(
|
|
390
394
|
_OperationIntent.DOWNLOAD, key, None, _head_and_read)
|
|
391
395
|
except PrivateNetworkRequired:
|
|
396
|
+
if required:
|
|
397
|
+
raise
|
|
392
398
|
logger.warning(
|
|
393
399
|
"Cannot read instance metadata JSON (private network required): %s",
|
|
394
400
|
key)
|
|
395
401
|
return {}
|
|
396
402
|
except Exception:
|
|
403
|
+
if required:
|
|
404
|
+
raise
|
|
397
405
|
logger.debug("Failed to read JSON object %s", key, exc_info=True)
|
|
398
406
|
return {}
|
|
399
407
|
|
|
400
408
|
|
|
409
|
+
def _apply_synthetic_content(inst: DatasetInstance, data: dict) -> None:
|
|
410
|
+
"""Reuse DatasetInstance's read normalization, including legacy tags handling."""
|
|
411
|
+
parsed = DatasetInstance.from_dict({
|
|
412
|
+
**data,
|
|
413
|
+
"instance_id": inst.instance_id,
|
|
414
|
+
"dataset": inst.dataset,
|
|
415
|
+
"dataset_version": inst.dataset_version,
|
|
416
|
+
"split": inst.split,
|
|
417
|
+
"registered": False,
|
|
418
|
+
})
|
|
419
|
+
inst.tags = parsed.tags
|
|
420
|
+
inst.difficulty = parsed.difficulty
|
|
421
|
+
inst.docker_image = parsed.docker_image
|
|
422
|
+
inst.extend_meta = parsed.extend_meta
|
|
423
|
+
|
|
424
|
+
|
|
401
425
|
# ---------------------------------------------------------------------------
|
|
402
426
|
# Synthetic builders
|
|
403
427
|
# ---------------------------------------------------------------------------
|
|
@@ -544,12 +568,9 @@ def build_synthetic_instances(
|
|
|
544
568
|
registered=False,
|
|
545
569
|
)
|
|
546
570
|
if read_content:
|
|
547
|
-
data = _read_json_object(content_store, key)
|
|
571
|
+
data = _read_json_object(content_store, key, required=True)
|
|
548
572
|
if data:
|
|
549
|
-
inst
|
|
550
|
-
inst.difficulty = data.get("difficulty")
|
|
551
|
-
inst.docker_image = data.get("docker_image")
|
|
552
|
-
inst.extend_meta = data.get("extend_meta", {})
|
|
573
|
+
_apply_synthetic_content(inst, data)
|
|
553
574
|
instances.append(inst)
|
|
554
575
|
|
|
555
576
|
return instances
|
|
@@ -705,6 +726,8 @@ def build_synthetic_instance(
|
|
|
705
726
|
keys = content_store.list_objects(
|
|
706
727
|
parent, list_only=True, dataset=dataset)
|
|
707
728
|
except Exception: # noqa: BLE001
|
|
729
|
+
if read_content:
|
|
730
|
+
raise
|
|
708
731
|
return None
|
|
709
732
|
|
|
710
733
|
if key not in keys:
|
|
@@ -725,11 +748,8 @@ def build_synthetic_instance(
|
|
|
725
748
|
except Exception: # noqa: BLE001, S110
|
|
726
749
|
pass # best-effort:manifest 枚举失败不阻断实例返回
|
|
727
750
|
if read_content:
|
|
728
|
-
data = _read_json_object(content_store, key)
|
|
751
|
+
data = _read_json_object(content_store, key, required=True)
|
|
729
752
|
if data:
|
|
730
|
-
inst
|
|
731
|
-
inst.difficulty = data.get("difficulty")
|
|
732
|
-
inst.docker_image = data.get("docker_image")
|
|
733
|
-
inst.extend_meta = data.get("extend_meta", {})
|
|
753
|
+
_apply_synthetic_content(inst, data)
|
|
734
754
|
|
|
735
755
|
return inst
|
|
@@ -65,7 +65,8 @@ class ApiMetadataRegistry(BaseMetadataRegistry):
|
|
|
65
65
|
def _q(self, dataset: str, version: str, split: str | None = None,
|
|
66
66
|
instance_id: str | None = None, *,
|
|
67
67
|
environment: str | None = None,
|
|
68
|
-
metadata_model: str | None = None
|
|
68
|
+
metadata_model: str | None = None,
|
|
69
|
+
allow_version_first: bool = False) -> str:
|
|
69
70
|
"""读路径查询串:与写路径(ingest)对称下发 metadata_model 与 environment。
|
|
70
71
|
|
|
71
72
|
两个字段都**必须**对称:写路径由 storage_env 派生 environment(dev→pre)写入 pre
|
|
@@ -80,7 +81,8 @@ class ApiMetadataRegistry(BaseMetadataRegistry):
|
|
|
80
81
|
"""
|
|
81
82
|
requested_model = (self._metadata_model if metadata_model is None
|
|
82
83
|
else metadata_model)
|
|
83
|
-
model = business_metadata_model(
|
|
84
|
+
model = business_metadata_model(
|
|
85
|
+
requested_model, allow_version_first=allow_version_first)
|
|
84
86
|
env = (self._environment if environment is None else environment).strip()
|
|
85
87
|
base = _q(dataset, version, split, instance_id,
|
|
86
88
|
keep_default_split=(model == "split_first"))
|
|
@@ -157,9 +159,11 @@ class ApiMetadataRegistry(BaseMetadataRegistry):
|
|
|
157
159
|
92001。只吞前者:数据集名拼错必须照常报错,否则会被静默当成"没有这个实例"。
|
|
158
160
|
"""
|
|
159
161
|
try:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
query = self._q(
|
|
163
|
+
dataset, version, split, instance_id,
|
|
164
|
+
environment=environment, metadata_model=metadata_model,
|
|
165
|
+
allow_version_first=True)
|
|
166
|
+
data = self._t.get(f"{self._BASE}/detail?{query}")
|
|
163
167
|
except TransportError as e:
|
|
164
168
|
if e.status == 404 and e.biz_code == CODE_INSTANCE_NOT_FOUND:
|
|
165
169
|
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo/clients/user_data_directories.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{instance_repo-1.4.7 → instance_repo-1.4.8.dev0}/instance_repo.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|