wandb 0.17.0rc2__py3-none-win32.whl → 0.17.2__py3-none-win32.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. wandb/__init__.py +4 -2
  2. wandb/apis/importers/internals/internal.py +0 -1
  3. wandb/apis/importers/wandb.py +12 -7
  4. wandb/apis/internal.py +0 -3
  5. wandb/apis/public/api.py +213 -79
  6. wandb/apis/public/artifacts.py +335 -100
  7. wandb/apis/public/files.py +9 -9
  8. wandb/apis/public/jobs.py +16 -4
  9. wandb/apis/public/projects.py +26 -28
  10. wandb/apis/public/query_generator.py +1 -1
  11. wandb/apis/public/runs.py +163 -65
  12. wandb/apis/public/sweeps.py +2 -2
  13. wandb/apis/reports/__init__.py +1 -7
  14. wandb/apis/reports/v1/__init__.py +5 -27
  15. wandb/apis/reports/v2/__init__.py +7 -19
  16. wandb/apis/workspaces/__init__.py +8 -0
  17. wandb/beta/workflows.py +8 -3
  18. wandb/bin/wandb-core +0 -0
  19. wandb/cli/cli.py +151 -59
  20. wandb/docker/__init__.py +1 -1
  21. wandb/errors/term.py +10 -2
  22. wandb/filesync/step_checksum.py +1 -4
  23. wandb/filesync/step_prepare.py +4 -24
  24. wandb/filesync/step_upload.py +5 -107
  25. wandb/filesync/upload_job.py +0 -76
  26. wandb/integration/gym/__init__.py +35 -15
  27. wandb/integration/openai/fine_tuning.py +21 -3
  28. wandb/integration/prodigy/prodigy.py +1 -1
  29. wandb/jupyter.py +16 -17
  30. wandb/old/summary.py +5 -0
  31. wandb/plot/pr_curve.py +2 -1
  32. wandb/plot/roc_curve.py +2 -1
  33. wandb/{plots → plot}/utils.py +13 -25
  34. wandb/proto/v3/wandb_internal_pb2.py +54 -54
  35. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  36. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  37. wandb/proto/v4/wandb_internal_pb2.py +54 -54
  38. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  39. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  40. wandb/proto/v5/wandb_base_pb2.py +30 -0
  41. wandb/proto/v5/wandb_internal_pb2.py +355 -0
  42. wandb/proto/v5/wandb_server_pb2.py +63 -0
  43. wandb/proto/v5/wandb_settings_pb2.py +45 -0
  44. wandb/proto/v5/wandb_telemetry_pb2.py +41 -0
  45. wandb/proto/wandb_base_pb2.py +2 -0
  46. wandb/proto/wandb_deprecated.py +9 -1
  47. wandb/proto/wandb_generate_deprecated.py +34 -0
  48. wandb/proto/{wandb_internal_codegen.py → wandb_generate_proto.py} +1 -35
  49. wandb/proto/wandb_internal_pb2.py +2 -0
  50. wandb/proto/wandb_server_pb2.py +2 -0
  51. wandb/proto/wandb_settings_pb2.py +2 -0
  52. wandb/proto/wandb_telemetry_pb2.py +2 -0
  53. wandb/sdk/artifacts/artifact.py +76 -23
  54. wandb/sdk/artifacts/artifact_manifest.py +1 -1
  55. wandb/sdk/artifacts/artifact_manifest_entry.py +6 -3
  56. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +1 -1
  57. wandb/sdk/artifacts/artifact_saver.py +1 -10
  58. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +6 -2
  59. wandb/sdk/artifacts/storage_handlers/multi_handler.py +1 -1
  60. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
  61. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +2 -42
  62. wandb/sdk/artifacts/storage_policy.py +1 -12
  63. wandb/sdk/data_types/_dtypes.py +5 -2
  64. wandb/sdk/data_types/html.py +1 -1
  65. wandb/sdk/data_types/image.py +1 -1
  66. wandb/sdk/data_types/object_3d.py +1 -1
  67. wandb/sdk/data_types/video.py +4 -2
  68. wandb/sdk/interface/interface.py +13 -0
  69. wandb/sdk/interface/interface_shared.py +1 -1
  70. wandb/sdk/internal/file_pusher.py +2 -5
  71. wandb/sdk/internal/file_stream.py +6 -19
  72. wandb/sdk/internal/internal_api.py +160 -138
  73. wandb/sdk/internal/job_builder.py +207 -135
  74. wandb/sdk/internal/progress.py +0 -28
  75. wandb/sdk/internal/sender.py +105 -42
  76. wandb/sdk/internal/settings_static.py +8 -1
  77. wandb/sdk/internal/system/assets/gpu.py +2 -0
  78. wandb/sdk/internal/system/assets/trainium.py +3 -3
  79. wandb/sdk/internal/system/system_info.py +4 -2
  80. wandb/sdk/internal/update.py +1 -1
  81. wandb/sdk/launch/__init__.py +9 -1
  82. wandb/sdk/launch/_launch.py +4 -24
  83. wandb/sdk/launch/_launch_add.py +1 -3
  84. wandb/sdk/launch/_project_spec.py +184 -224
  85. wandb/sdk/launch/agent/agent.py +58 -18
  86. wandb/sdk/launch/agent/config.py +0 -3
  87. wandb/sdk/launch/builder/abstract.py +67 -0
  88. wandb/sdk/launch/builder/build.py +165 -576
  89. wandb/sdk/launch/builder/context_manager.py +235 -0
  90. wandb/sdk/launch/builder/docker_builder.py +7 -23
  91. wandb/sdk/launch/builder/kaniko_builder.py +10 -23
  92. wandb/sdk/launch/builder/templates/dockerfile.py +92 -0
  93. wandb/sdk/launch/create_job.py +51 -45
  94. wandb/sdk/launch/environment/aws_environment.py +26 -1
  95. wandb/sdk/launch/inputs/files.py +148 -0
  96. wandb/sdk/launch/inputs/internal.py +224 -0
  97. wandb/sdk/launch/inputs/manage.py +95 -0
  98. wandb/sdk/launch/runner/abstract.py +2 -2
  99. wandb/sdk/launch/runner/kubernetes_monitor.py +45 -12
  100. wandb/sdk/launch/runner/kubernetes_runner.py +6 -8
  101. wandb/sdk/launch/runner/local_container.py +2 -3
  102. wandb/sdk/launch/runner/local_process.py +8 -29
  103. wandb/sdk/launch/runner/sagemaker_runner.py +20 -14
  104. wandb/sdk/launch/runner/vertex_runner.py +8 -7
  105. wandb/sdk/launch/sweeps/scheduler.py +2 -0
  106. wandb/sdk/launch/sweeps/utils.py +2 -2
  107. wandb/sdk/launch/utils.py +16 -138
  108. wandb/sdk/lib/_settings_toposort_generated.py +2 -5
  109. wandb/sdk/lib/apikey.py +4 -2
  110. wandb/sdk/lib/config_util.py +3 -3
  111. wandb/sdk/lib/proto_util.py +22 -1
  112. wandb/sdk/lib/redirect.py +1 -1
  113. wandb/sdk/service/service.py +2 -1
  114. wandb/sdk/service/streams.py +5 -5
  115. wandb/sdk/wandb_init.py +25 -59
  116. wandb/sdk/wandb_login.py +28 -25
  117. wandb/sdk/wandb_run.py +135 -70
  118. wandb/sdk/wandb_settings.py +33 -64
  119. wandb/sdk/wandb_watch.py +1 -1
  120. wandb/sklearn/plot/classifier.py +4 -6
  121. wandb/sync/sync.py +2 -2
  122. wandb/testing/relay.py +32 -17
  123. wandb/util.py +39 -37
  124. wandb/wandb_agent.py +3 -3
  125. wandb/wandb_controller.py +3 -2
  126. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/METADATA +7 -9
  127. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/RECORD +130 -152
  128. wandb/apis/reports/v1/_blocks.py +0 -1406
  129. wandb/apis/reports/v1/_helpers.py +0 -70
  130. wandb/apis/reports/v1/_panels.py +0 -1282
  131. wandb/apis/reports/v1/_templates.py +0 -478
  132. wandb/apis/reports/v1/blocks.py +0 -27
  133. wandb/apis/reports/v1/helpers.py +0 -2
  134. wandb/apis/reports/v1/mutations.py +0 -66
  135. wandb/apis/reports/v1/panels.py +0 -17
  136. wandb/apis/reports/v1/report.py +0 -268
  137. wandb/apis/reports/v1/runset.py +0 -144
  138. wandb/apis/reports/v1/templates.py +0 -7
  139. wandb/apis/reports/v1/util.py +0 -406
  140. wandb/apis/reports/v1/validators.py +0 -131
  141. wandb/apis/reports/v2/blocks.py +0 -25
  142. wandb/apis/reports/v2/expr_parsing.py +0 -257
  143. wandb/apis/reports/v2/gql.py +0 -68
  144. wandb/apis/reports/v2/interface.py +0 -1911
  145. wandb/apis/reports/v2/internal.py +0 -867
  146. wandb/apis/reports/v2/metrics.py +0 -6
  147. wandb/apis/reports/v2/panels.py +0 -15
  148. wandb/catboost/__init__.py +0 -9
  149. wandb/fastai/__init__.py +0 -9
  150. wandb/keras/__init__.py +0 -19
  151. wandb/lightgbm/__init__.py +0 -9
  152. wandb/plots/__init__.py +0 -6
  153. wandb/plots/explain_text.py +0 -36
  154. wandb/plots/heatmap.py +0 -81
  155. wandb/plots/named_entity.py +0 -43
  156. wandb/plots/part_of_speech.py +0 -50
  157. wandb/plots/plot_definitions.py +0 -768
  158. wandb/plots/precision_recall.py +0 -121
  159. wandb/plots/roc.py +0 -103
  160. wandb/sacred/__init__.py +0 -3
  161. wandb/xgboost/__init__.py +0 -9
  162. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/WHEEL +0 -0
  163. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/entry_points.txt +0 -0
  164. {wandb-0.17.0rc2.dist-info → wandb-0.17.2.dist-info}/licenses/LICENSE +0 -0
@@ -14,7 +14,7 @@ from wandb.sdk.lib.paths import LogicalPath
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from wandb.sdk.artifacts.artifact_manifest import ArtifactManifest
17
- from wandb.sdk.artifacts.artifact_saver import SaveFn, SaveFnAsync
17
+ from wandb.sdk.artifacts.artifact_saver import SaveFn
18
18
  from wandb.sdk.internal import file_stream, internal_api
19
19
  from wandb.sdk.internal.settings_static import SettingsStatic
20
20
 
@@ -148,11 +148,8 @@ class FilePusher:
148
148
  manifest: "ArtifactManifest",
149
149
  artifact_id: str,
150
150
  save_fn: "SaveFn",
151
- save_fn_async: "SaveFnAsync",
152
151
  ) -> None:
153
- event = step_checksum.RequestStoreManifestFiles(
154
- manifest, artifact_id, save_fn, save_fn_async
155
- )
152
+ event = step_checksum.RequestStoreManifestFiles(manifest, artifact_id, save_fn)
156
153
  self._incoming_queue.put(event)
157
154
 
158
155
  def commit_artifact(
@@ -1,4 +1,3 @@
1
- import base64
2
1
  import functools
3
2
  import itertools
4
3
  import json
@@ -53,7 +52,7 @@ logger = logging.getLogger(__name__)
53
52
 
54
53
  class Chunk(NamedTuple):
55
54
  filename: str
56
- data: Any
55
+ data: str
57
56
 
58
57
 
59
58
  class DefaultFilePolicy:
@@ -227,7 +226,7 @@ class CRDedupeFilePolicy(DefaultFilePolicy):
227
226
  prefix += token + " "
228
227
  return prefix, rest
229
228
 
230
- def process_chunks(self, chunks: List) -> List["ProcessedChunk"]:
229
+ def process_chunks(self, chunks: List[Chunk]) -> List["ProcessedChunk"]:
231
230
  r"""Process chunks.
232
231
 
233
232
  Args:
@@ -300,18 +299,6 @@ class CRDedupeFilePolicy(DefaultFilePolicy):
300
299
  return ret
301
300
 
302
301
 
303
- class BinaryFilePolicy(DefaultFilePolicy):
304
- def __init__(self) -> None:
305
- super().__init__()
306
- self._offset: int = 0
307
-
308
- def process_chunks(self, chunks: List[Chunk]) -> "ProcessedBinaryChunk":
309
- data = b"".join([c.data for c in chunks])
310
- enc = base64.b64encode(data).decode("ascii")
311
- self._offset += len(data)
312
- return {"offset": self._offset, "content": enc, "encoding": "base64"}
313
-
314
-
315
302
  class FileStreamApi:
316
303
  """Pushes chunks of files to our streaming endpoint.
317
304
 
@@ -520,7 +507,7 @@ class FileStreamApi:
520
507
  wandb.termerror(
521
508
  "Dropped streaming file chunk (see wandb/debug-internal.log)"
522
509
  )
523
- logger.exception("dropped chunk %s" % response)
510
+ logger.exception("dropped chunk {}".format(response))
524
511
  self._dropped_chunks += 1
525
512
  else:
526
513
  parsed: Optional[dict] = None
@@ -585,12 +572,12 @@ class FileStreamApi:
585
572
  def enqueue_preempting(self) -> None:
586
573
  self._queue.put(self.Preempting())
587
574
 
588
- def push(self, filename: str, data: Any) -> None:
575
+ def push(self, filename: str, data: str) -> None:
589
576
  """Push a chunk of a file to the streaming endpoint.
590
577
 
591
578
  Arguments:
592
- filename: Name of file that this is a chunk of.
593
- data: File data.
579
+ filename: Name of file to append to.
580
+ data: Text to append to the file.
594
581
  """
595
582
  self._queue.put(Chunk(filename, data))
596
583
 
@@ -1,5 +1,4 @@
1
1
  import ast
2
- import asyncio
3
2
  import base64
4
3
  import datetime
5
4
  import functools
@@ -49,7 +48,7 @@ from ..lib import retry
49
48
  from ..lib.filenames import DIFF_FNAME, METADATA_FNAME
50
49
  from ..lib.gitlib import GitRepo
51
50
  from . import context
52
- from .progress import AsyncProgress, Progress
51
+ from .progress import Progress
53
52
 
54
53
  logger = logging.getLogger(__name__)
55
54
 
@@ -121,13 +120,6 @@ if TYPE_CHECKING:
121
120
  SweepState = Literal["RUNNING", "PAUSED", "CANCELED", "FINISHED"]
122
121
  Number = Union[int, float]
123
122
 
124
- # This funny if/else construction is the simplest thing I've found that
125
- # works at runtime, satisfies Mypy, and gives autocomplete in VSCode:
126
- if TYPE_CHECKING:
127
- import httpx
128
- else:
129
- httpx = util.get_module("httpx")
130
-
131
123
  # class _MappingSupportsCopy(Protocol):
132
124
  # def copy(self) -> "_MappingSupportsCopy": ...
133
125
  # def keys(self) -> Iterable: ...
@@ -161,23 +153,6 @@ def check_httpclient_logger_handler() -> None:
161
153
  httpclient_logger.addHandler(root_logger.handlers[0])
162
154
 
163
155
 
164
- def check_httpx_exc_retriable(exc: Exception) -> bool:
165
- retriable_codes = (308, 408, 409, 429, 500, 502, 503, 504)
166
- return (
167
- isinstance(exc, (httpx.TimeoutException, httpx.NetworkError))
168
- or (
169
- isinstance(exc, httpx.HTTPStatusError)
170
- and exc.response.status_code in retriable_codes
171
- )
172
- or (
173
- isinstance(exc, httpx.HTTPStatusError)
174
- and exc.response.status_code == 400
175
- and "x-amz-meta-md5" in exc.request.headers
176
- and "RequestTimeout" in str(exc.response.content)
177
- )
178
- )
179
-
180
-
181
156
  class _ThreadLocalData(threading.local):
182
157
  context: Optional[context.Context]
183
158
 
@@ -286,10 +261,6 @@ class Api:
286
261
  )
287
262
  )
288
263
 
289
- # httpx is an optional dependency, so we lazily instantiate the client
290
- # only when we need it
291
- self._async_httpx_client: Optional[httpx.AsyncClient] = None
292
-
293
264
  self.retry_callback = retry_callback
294
265
  self._retry_gql = retry.Retry(
295
266
  self.execute,
@@ -361,7 +332,7 @@ class Api:
361
332
 
362
333
  def relocate(self) -> None:
363
334
  """Ensure the current api points to the right server."""
364
- self.client.transport.url = "%s/graphql" % self.settings("base_url")
335
+ self.client.transport.url = "{}/graphql".format(self.settings("base_url"))
365
336
 
366
337
  def execute(self, *args: Any, **kwargs: Any) -> "_Response":
367
338
  """Wrapper around execute that logs in cases of failure."""
@@ -1209,6 +1180,7 @@ class Api:
1209
1180
  project_name (str): The project to download, (can include bucket)
1210
1181
  name (str): The run to download
1211
1182
  """
1183
+ # Pulling wandbConfig.start_time is required so that we can determine if a run has actually started
1212
1184
  query = gql(
1213
1185
  """
1214
1186
  query RunResumeStatus($project: String, $entity: String, $name: String!) {
@@ -1232,6 +1204,7 @@ class Api:
1232
1204
  eventsTail
1233
1205
  config
1234
1206
  tags
1207
+ wandbConfig(keys: ["t"])
1235
1208
  }
1236
1209
  }
1237
1210
  }
@@ -2245,6 +2218,113 @@ class Api:
2245
2218
  server_messages,
2246
2219
  )
2247
2220
 
2221
+ @normalize_exceptions
2222
+ def rewind_run(
2223
+ self,
2224
+ run_name: str,
2225
+ metric_name: str,
2226
+ metric_value: float,
2227
+ program_path: Optional[str] = None,
2228
+ entity: Optional[str] = None,
2229
+ project: Optional[str] = None,
2230
+ num_retries: Optional[int] = None,
2231
+ ) -> dict:
2232
+ """Rewinds a run to a previous state.
2233
+
2234
+ Arguments:
2235
+ run_name (str): The name of the run to rewind
2236
+ metric_name (str): The name of the metric to rewind to
2237
+ metric_value (float): The value of the metric to rewind to
2238
+ program_path (str, optional): Path to the program
2239
+ entity (str, optional): The entity to scope this project to
2240
+ project (str, optional): The name of the project
2241
+ num_retries (int, optional): Number of retries
2242
+
2243
+ Returns:
2244
+ A dict with the rewound run
2245
+
2246
+ {
2247
+ "id": "run_id",
2248
+ "name": "run_name",
2249
+ "displayName": "run_display_name",
2250
+ "description": "run_description",
2251
+ "config": "stringified_run_config_json",
2252
+ "sweepName": "run_sweep_name",
2253
+ "project": {
2254
+ "id": "project_id",
2255
+ "name": "project_name",
2256
+ "entity": {
2257
+ "id": "entity_id",
2258
+ "name": "entity_name"
2259
+ }
2260
+ },
2261
+ "historyLineCount": 100,
2262
+ }
2263
+ """
2264
+ query_string = """
2265
+ mutation RewindRun($runName: String!, $entity: String, $project: String, $metricName: String!, $metricValue: Float!) {
2266
+ rewindRun(input: {runName: $runName, entityName: $entity, projectName: $project, metricName: $metricName, metricValue: $metricValue}) {
2267
+ rewoundRun {
2268
+ id
2269
+ name
2270
+ displayName
2271
+ description
2272
+ config
2273
+ sweepName
2274
+ project {
2275
+ id
2276
+ name
2277
+ entity {
2278
+ id
2279
+ name
2280
+ }
2281
+ }
2282
+ historyLineCount
2283
+ }
2284
+ }
2285
+ }
2286
+ """
2287
+
2288
+ mutation = gql(query_string)
2289
+
2290
+ kwargs = {}
2291
+ if num_retries is not None:
2292
+ kwargs["num_retries"] = num_retries
2293
+
2294
+ variable_values = {
2295
+ "runName": run_name,
2296
+ "entity": entity or self.settings("entity"),
2297
+ "project": project or util.auto_project_name(program_path),
2298
+ "metricName": metric_name,
2299
+ "metricValue": metric_value,
2300
+ }
2301
+
2302
+ # retry conflict errors for 2 minutes, default to no_auth_retry
2303
+ check_retry_fn = util.make_check_retry_fn(
2304
+ check_fn=util.check_retry_conflict_or_gone,
2305
+ check_timedelta=datetime.timedelta(minutes=2),
2306
+ fallback_retry_fn=util.no_retry_auth,
2307
+ )
2308
+
2309
+ response = self.gql(
2310
+ mutation,
2311
+ variable_values=variable_values,
2312
+ check_retry_fn=check_retry_fn,
2313
+ **kwargs,
2314
+ )
2315
+
2316
+ run_obj: Dict[str, Dict[str, Dict[str, str]]] = response.get(
2317
+ "rewindRun", {}
2318
+ ).get("rewoundRun", {})
2319
+ project_obj: Dict[str, Dict[str, str]] = run_obj.get("project", {})
2320
+ if project_obj:
2321
+ self.set_setting("project", project_obj["name"])
2322
+ entity_obj = project_obj.get("entity", {})
2323
+ if entity_obj:
2324
+ self.set_setting("entity", entity_obj["name"])
2325
+
2326
+ return run_obj
2327
+
2248
2328
  @normalize_exceptions
2249
2329
  def get_run_info(
2250
2330
  self,
@@ -2794,105 +2874,6 @@ class Api:
2794
2874
 
2795
2875
  return response
2796
2876
 
2797
- async def upload_file_async(
2798
- self,
2799
- url: str,
2800
- file: IO[bytes],
2801
- callback: Optional["ProgressFn"] = None,
2802
- extra_headers: Optional[Dict[str, str]] = None,
2803
- ) -> None:
2804
- """An async not-quite-equivalent version of `upload_file`.
2805
-
2806
- Differences from `upload_file`:
2807
- - This method doesn't implement Azure uploads. (The Azure SDK supports
2808
- async, but it's nontrivial to use it here.) If the upload looks like
2809
- it's destined for Azure, this method will delegate to the sync impl.
2810
- - Consequently, this method doesn't return the response object.
2811
- (Because it might fall back to the sync impl, it would sometimes
2812
- return a `requests.Response` and sometimes an `httpx.Response`.)
2813
- - This method doesn't wrap retryable errors in `TransientError`.
2814
- It leaves that determination to the caller.
2815
- """
2816
- check_httpclient_logger_handler()
2817
- must_delegate = False
2818
-
2819
- if httpx is None:
2820
- wandb.termwarn( # type: ignore[unreachable]
2821
- "async file-uploads require `pip install wandb[async]`; falling back to sync implementation",
2822
- repeat=False,
2823
- )
2824
- must_delegate = True
2825
-
2826
- if extra_headers is not None and "x-ms-blob-type" in extra_headers:
2827
- wandb.termwarn(
2828
- "async file-uploads don't support Azure; falling back to sync implementation",
2829
- repeat=False,
2830
- )
2831
- must_delegate = True
2832
-
2833
- if must_delegate:
2834
- await asyncio.get_event_loop().run_in_executor(
2835
- None,
2836
- lambda: self.upload_file_retry(
2837
- url=url,
2838
- file=file,
2839
- callback=callback,
2840
- extra_headers=extra_headers,
2841
- ),
2842
- )
2843
- return
2844
-
2845
- if self._async_httpx_client is None:
2846
- self._async_httpx_client = httpx.AsyncClient()
2847
-
2848
- progress = AsyncProgress(Progress(file, callback=callback))
2849
-
2850
- try:
2851
- response = await self._async_httpx_client.put(
2852
- url=url,
2853
- content=progress,
2854
- headers={
2855
- "Content-Length": str(len(progress)),
2856
- **(extra_headers if extra_headers is not None else {}),
2857
- },
2858
- )
2859
- response.raise_for_status()
2860
- except Exception as e:
2861
- progress.rewind()
2862
- logger.error(f"upload_file_async exception {url}: {e}")
2863
- if isinstance(e, httpx.RequestError):
2864
- logger.error(f"upload_file_async request headers: {e.request.headers}")
2865
- if isinstance(e, httpx.HTTPStatusError):
2866
- logger.error(f"upload_file_async response body: {e.response.content!r}")
2867
- raise
2868
-
2869
- async def upload_file_retry_async(
2870
- self,
2871
- url: str,
2872
- file: IO[bytes],
2873
- callback: Optional["ProgressFn"] = None,
2874
- extra_headers: Optional[Dict[str, str]] = None,
2875
- num_retries: int = 100,
2876
- ) -> None:
2877
- backoff = retry.FilteredBackoff(
2878
- filter=check_httpx_exc_retriable,
2879
- wrapped=retry.ExponentialBackoff(
2880
- initial_sleep=datetime.timedelta(seconds=1),
2881
- max_sleep=datetime.timedelta(seconds=60),
2882
- max_retries=num_retries,
2883
- timeout_at=datetime.datetime.now() + datetime.timedelta(days=7),
2884
- ),
2885
- )
2886
-
2887
- await retry.retry_async(
2888
- backoff=backoff,
2889
- fn=self.upload_file_async,
2890
- url=url,
2891
- file=file,
2892
- callback=callback,
2893
- extra_headers=extra_headers,
2894
- )
2895
-
2896
2877
  @normalize_exceptions
2897
2878
  def register_agent(
2898
2879
  self,
@@ -3039,9 +3020,10 @@ class Api:
3039
3020
  parameter["distribution"] = "uniform"
3040
3021
  else:
3041
3022
  raise ValueError(
3042
- "Parameter %s is ambiguous, please specify bounds as both floats (for a float_"
3043
- "uniform distribution) or ints (for an int_uniform distribution)."
3044
- % parameter_name
3023
+ "Parameter {} is ambiguous, please specify bounds as both floats (for a float_"
3024
+ "uniform distribution) or ints (for an int_uniform distribution).".format(
3025
+ parameter_name
3026
+ )
3045
3027
  )
3046
3028
  return config
3047
3029
 
@@ -3056,6 +3038,7 @@ class Api:
3056
3038
  project: Optional[str] = None,
3057
3039
  entity: Optional[str] = None,
3058
3040
  state: Optional[str] = None,
3041
+ prior_runs: Optional[List[str]] = None,
3059
3042
  ) -> Tuple[str, List[str]]:
3060
3043
  """Upsert a sweep object.
3061
3044
 
@@ -3068,6 +3051,7 @@ class Api:
3068
3051
  project (str): project to use
3069
3052
  entity (str): entity to use
3070
3053
  state (str): state
3054
+ prior_runs (list): IDs of existing runs to add to the sweep
3071
3055
  """
3072
3056
  project_query = """
3073
3057
  project {
@@ -3088,7 +3072,8 @@ class Api:
3088
3072
  $projectName: String,
3089
3073
  $controller: JSONString,
3090
3074
  $scheduler: JSONString,
3091
- $state: String
3075
+ $state: String,
3076
+ $priorRunsFilters: JSONString,
3092
3077
  ) {
3093
3078
  upsertSweep(input: {
3094
3079
  id: $id,
@@ -3098,7 +3083,8 @@ class Api:
3098
3083
  projectName: $projectName,
3099
3084
  controller: $controller,
3100
3085
  scheduler: $scheduler,
3101
- state: $state
3086
+ state: $state,
3087
+ priorRunsFilters: $priorRunsFilters,
3102
3088
  }) {
3103
3089
  sweep {
3104
3090
  name
@@ -3144,7 +3130,12 @@ class Api:
3144
3130
 
3145
3131
  # Silly, but attr-dicts like EasyDicts don't serialize correctly to yaml.
3146
3132
  # This sanitizes them with a round trip pass through json to get a regular dict.
3147
- config_str = yaml.dump(json.loads(json.dumps(config)))
3133
+ config_str = yaml.dump(
3134
+ json.loads(json.dumps(config)), Dumper=util.NonOctalStringDumper
3135
+ )
3136
+ filters = None
3137
+ if prior_runs:
3138
+ filters = json.dumps({"$or": [{"name": r} for r in prior_runs]})
3148
3139
 
3149
3140
  err: Optional[Exception] = None
3150
3141
  for mutation in mutations:
@@ -3158,6 +3149,7 @@ class Api:
3158
3149
  "controller": controller,
3159
3150
  "launchScheduler": launch_scheduler,
3160
3151
  "scheduler": scheduler,
3152
+ "priorRunsFilters": filters,
3161
3153
  }
3162
3154
  if state:
3163
3155
  variables["state"] = state
@@ -3887,6 +3879,36 @@ class Api:
3887
3879
  response["updateArtifactManifest"]["artifactManifest"]["file"],
3888
3880
  )
3889
3881
 
3882
+ def update_artifact_metadata(
3883
+ self, artifact_id: str, metadata: Dict[str, Any]
3884
+ ) -> Dict[str, Any]:
3885
+ """Set the metadata of the given artifact version."""
3886
+ mutation = gql(
3887
+ """
3888
+ mutation UpdateArtifact(
3889
+ $artifactID: ID!,
3890
+ $metadata: JSONString,
3891
+ ) {
3892
+ updateArtifact(input: {
3893
+ artifactID: $artifactID,
3894
+ metadata: $metadata,
3895
+ }) {
3896
+ artifact {
3897
+ id
3898
+ }
3899
+ }
3900
+ }
3901
+ """
3902
+ )
3903
+ response = self.gql(
3904
+ mutation,
3905
+ variable_values={
3906
+ "artifactID": artifact_id,
3907
+ "metadata": json.dumps(metadata),
3908
+ },
3909
+ )
3910
+ return response["updateArtifact"]["artifact"]
3911
+
3890
3912
  def _resolve_client_id(
3891
3913
  self,
3892
3914
  client_id: str,
@@ -4081,9 +4103,9 @@ class Api:
4081
4103
  s = self.sweep(sweep=sweep, entity=entity, project=project, specs="{}")
4082
4104
  curr_state = s["state"].upper()
4083
4105
  if state == "PAUSED" and curr_state not in ("PAUSED", "RUNNING"):
4084
- raise Exception("Cannot pause %s sweep." % curr_state.lower())
4106
+ raise Exception("Cannot pause {} sweep.".format(curr_state.lower()))
4085
4107
  elif state != "RUNNING" and curr_state not in ("RUNNING", "PAUSED", "PENDING"):
4086
- raise Exception("Sweep already %s." % curr_state.lower())
4108
+ raise Exception("Sweep already {}.".format(curr_state.lower()))
4087
4109
  sweep_id = s["id"]
4088
4110
  mutation = gql(
4089
4111
  """