wandb 0.18.2__py3-none-musllinux_1_2_x86_64.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.
Files changed (827) hide show
  1. package_readme.md +89 -0
  2. wandb/__init__.py +245 -0
  3. wandb/__init__.pyi +1139 -0
  4. wandb/__main__.py +3 -0
  5. wandb/_globals.py +19 -0
  6. wandb/agents/__init__.py +0 -0
  7. wandb/agents/pyagent.py +363 -0
  8. wandb/analytics/__init__.py +3 -0
  9. wandb/analytics/sentry.py +266 -0
  10. wandb/apis/__init__.py +48 -0
  11. wandb/apis/attrs.py +40 -0
  12. wandb/apis/importers/__init__.py +1 -0
  13. wandb/apis/importers/internals/internal.py +385 -0
  14. wandb/apis/importers/internals/protocols.py +99 -0
  15. wandb/apis/importers/internals/util.py +78 -0
  16. wandb/apis/importers/mlflow.py +254 -0
  17. wandb/apis/importers/validation.py +108 -0
  18. wandb/apis/importers/wandb.py +1603 -0
  19. wandb/apis/internal.py +232 -0
  20. wandb/apis/normalize.py +89 -0
  21. wandb/apis/paginator.py +81 -0
  22. wandb/apis/public/__init__.py +34 -0
  23. wandb/apis/public/api.py +1305 -0
  24. wandb/apis/public/artifacts.py +1090 -0
  25. wandb/apis/public/const.py +4 -0
  26. wandb/apis/public/files.py +195 -0
  27. wandb/apis/public/history.py +149 -0
  28. wandb/apis/public/jobs.py +659 -0
  29. wandb/apis/public/projects.py +154 -0
  30. wandb/apis/public/query_generator.py +166 -0
  31. wandb/apis/public/reports.py +469 -0
  32. wandb/apis/public/runs.py +914 -0
  33. wandb/apis/public/sweeps.py +240 -0
  34. wandb/apis/public/teams.py +198 -0
  35. wandb/apis/public/users.py +136 -0
  36. wandb/apis/reports/__init__.py +1 -0
  37. wandb/apis/reports/v1/__init__.py +8 -0
  38. wandb/apis/reports/v2/__init__.py +8 -0
  39. wandb/apis/workspaces/__init__.py +8 -0
  40. wandb/beta/workflows.py +288 -0
  41. wandb/bin/nvidia_gpu_stats +0 -0
  42. wandb/bin/wandb-core +0 -0
  43. wandb/cli/__init__.py +0 -0
  44. wandb/cli/cli.py +3004 -0
  45. wandb/data_types.py +63 -0
  46. wandb/docker/__init__.py +342 -0
  47. wandb/docker/auth.py +436 -0
  48. wandb/docker/wandb-entrypoint.sh +33 -0
  49. wandb/docker/www_authenticate.py +94 -0
  50. wandb/env.py +514 -0
  51. wandb/errors/__init__.py +17 -0
  52. wandb/errors/errors.py +37 -0
  53. wandb/errors/term.py +103 -0
  54. wandb/errors/util.py +57 -0
  55. wandb/errors/warnings.py +2 -0
  56. wandb/filesync/__init__.py +0 -0
  57. wandb/filesync/dir_watcher.py +403 -0
  58. wandb/filesync/stats.py +100 -0
  59. wandb/filesync/step_checksum.py +142 -0
  60. wandb/filesync/step_prepare.py +179 -0
  61. wandb/filesync/step_upload.py +290 -0
  62. wandb/filesync/upload_job.py +142 -0
  63. wandb/integration/__init__.py +0 -0
  64. wandb/integration/catboost/__init__.py +5 -0
  65. wandb/integration/catboost/catboost.py +178 -0
  66. wandb/integration/cohere/__init__.py +3 -0
  67. wandb/integration/cohere/cohere.py +21 -0
  68. wandb/integration/cohere/resolver.py +347 -0
  69. wandb/integration/diffusers/__init__.py +3 -0
  70. wandb/integration/diffusers/autologger.py +76 -0
  71. wandb/integration/diffusers/pipeline_resolver.py +50 -0
  72. wandb/integration/diffusers/resolvers/__init__.py +9 -0
  73. wandb/integration/diffusers/resolvers/multimodal.py +882 -0
  74. wandb/integration/diffusers/resolvers/utils.py +102 -0
  75. wandb/integration/fastai/__init__.py +249 -0
  76. wandb/integration/gym/__init__.py +105 -0
  77. wandb/integration/huggingface/__init__.py +3 -0
  78. wandb/integration/huggingface/huggingface.py +18 -0
  79. wandb/integration/huggingface/resolver.py +213 -0
  80. wandb/integration/keras/__init__.py +11 -0
  81. wandb/integration/keras/callbacks/__init__.py +5 -0
  82. wandb/integration/keras/callbacks/metrics_logger.py +136 -0
  83. wandb/integration/keras/callbacks/model_checkpoint.py +195 -0
  84. wandb/integration/keras/callbacks/tables_builder.py +226 -0
  85. wandb/integration/keras/keras.py +1091 -0
  86. wandb/integration/kfp/__init__.py +6 -0
  87. wandb/integration/kfp/helpers.py +28 -0
  88. wandb/integration/kfp/kfp_patch.py +324 -0
  89. wandb/integration/kfp/wandb_logging.py +182 -0
  90. wandb/integration/langchain/__init__.py +3 -0
  91. wandb/integration/langchain/wandb_tracer.py +48 -0
  92. wandb/integration/lightgbm/__init__.py +239 -0
  93. wandb/integration/lightning/__init__.py +0 -0
  94. wandb/integration/lightning/fabric/__init__.py +3 -0
  95. wandb/integration/lightning/fabric/logger.py +762 -0
  96. wandb/integration/magic.py +556 -0
  97. wandb/integration/metaflow/__init__.py +3 -0
  98. wandb/integration/metaflow/metaflow.py +383 -0
  99. wandb/integration/openai/__init__.py +3 -0
  100. wandb/integration/openai/fine_tuning.py +480 -0
  101. wandb/integration/openai/openai.py +22 -0
  102. wandb/integration/openai/resolver.py +240 -0
  103. wandb/integration/prodigy/__init__.py +3 -0
  104. wandb/integration/prodigy/prodigy.py +299 -0
  105. wandb/integration/sacred/__init__.py +117 -0
  106. wandb/integration/sagemaker/__init__.py +12 -0
  107. wandb/integration/sagemaker/auth.py +28 -0
  108. wandb/integration/sagemaker/config.py +49 -0
  109. wandb/integration/sagemaker/files.py +3 -0
  110. wandb/integration/sagemaker/resources.py +34 -0
  111. wandb/integration/sb3/__init__.py +3 -0
  112. wandb/integration/sb3/sb3.py +153 -0
  113. wandb/integration/sklearn/__init__.py +37 -0
  114. wandb/integration/sklearn/calculate/__init__.py +32 -0
  115. wandb/integration/sklearn/calculate/calibration_curves.py +125 -0
  116. wandb/integration/sklearn/calculate/class_proportions.py +68 -0
  117. wandb/integration/sklearn/calculate/confusion_matrix.py +93 -0
  118. wandb/integration/sklearn/calculate/decision_boundaries.py +40 -0
  119. wandb/integration/sklearn/calculate/elbow_curve.py +55 -0
  120. wandb/integration/sklearn/calculate/feature_importances.py +67 -0
  121. wandb/integration/sklearn/calculate/learning_curve.py +64 -0
  122. wandb/integration/sklearn/calculate/outlier_candidates.py +69 -0
  123. wandb/integration/sklearn/calculate/residuals.py +86 -0
  124. wandb/integration/sklearn/calculate/silhouette.py +118 -0
  125. wandb/integration/sklearn/calculate/summary_metrics.py +62 -0
  126. wandb/integration/sklearn/plot/__init__.py +35 -0
  127. wandb/integration/sklearn/plot/classifier.py +329 -0
  128. wandb/integration/sklearn/plot/clusterer.py +146 -0
  129. wandb/integration/sklearn/plot/regressor.py +121 -0
  130. wandb/integration/sklearn/plot/shared.py +91 -0
  131. wandb/integration/sklearn/utils.py +183 -0
  132. wandb/integration/tensorboard/__init__.py +10 -0
  133. wandb/integration/tensorboard/log.py +355 -0
  134. wandb/integration/tensorboard/monkeypatch.py +185 -0
  135. wandb/integration/tensorflow/__init__.py +5 -0
  136. wandb/integration/tensorflow/estimator_hook.py +54 -0
  137. wandb/integration/torch/__init__.py +0 -0
  138. wandb/integration/torch/wandb_torch.py +554 -0
  139. wandb/integration/ultralytics/__init__.py +11 -0
  140. wandb/integration/ultralytics/bbox_utils.py +208 -0
  141. wandb/integration/ultralytics/callback.py +524 -0
  142. wandb/integration/ultralytics/classification_utils.py +83 -0
  143. wandb/integration/ultralytics/mask_utils.py +202 -0
  144. wandb/integration/ultralytics/pose_utils.py +103 -0
  145. wandb/integration/xgboost/__init__.py +11 -0
  146. wandb/integration/xgboost/xgboost.py +189 -0
  147. wandb/integration/yolov8/__init__.py +0 -0
  148. wandb/integration/yolov8/yolov8.py +284 -0
  149. wandb/jupyter.py +515 -0
  150. wandb/magic.py +3 -0
  151. wandb/mpmain/__init__.py +0 -0
  152. wandb/mpmain/__main__.py +1 -0
  153. wandb/old/__init__.py +0 -0
  154. wandb/old/core.py +53 -0
  155. wandb/old/settings.py +173 -0
  156. wandb/old/summary.py +440 -0
  157. wandb/plot/__init__.py +19 -0
  158. wandb/plot/bar.py +45 -0
  159. wandb/plot/confusion_matrix.py +100 -0
  160. wandb/plot/histogram.py +39 -0
  161. wandb/plot/line.py +43 -0
  162. wandb/plot/line_series.py +88 -0
  163. wandb/plot/pr_curve.py +136 -0
  164. wandb/plot/roc_curve.py +118 -0
  165. wandb/plot/scatter.py +32 -0
  166. wandb/plot/utils.py +183 -0
  167. wandb/plot/viz.py +123 -0
  168. wandb/proto/__init__.py +0 -0
  169. wandb/proto/v3/__init__.py +0 -0
  170. wandb/proto/v3/wandb_base_pb2.py +55 -0
  171. wandb/proto/v3/wandb_internal_pb2.py +1608 -0
  172. wandb/proto/v3/wandb_server_pb2.py +208 -0
  173. wandb/proto/v3/wandb_settings_pb2.py +112 -0
  174. wandb/proto/v3/wandb_telemetry_pb2.py +106 -0
  175. wandb/proto/v4/__init__.py +0 -0
  176. wandb/proto/v4/wandb_base_pb2.py +30 -0
  177. wandb/proto/v4/wandb_internal_pb2.py +360 -0
  178. wandb/proto/v4/wandb_server_pb2.py +63 -0
  179. wandb/proto/v4/wandb_settings_pb2.py +45 -0
  180. wandb/proto/v4/wandb_telemetry_pb2.py +41 -0
  181. wandb/proto/v5/wandb_base_pb2.py +31 -0
  182. wandb/proto/v5/wandb_internal_pb2.py +361 -0
  183. wandb/proto/v5/wandb_server_pb2.py +64 -0
  184. wandb/proto/v5/wandb_settings_pb2.py +46 -0
  185. wandb/proto/v5/wandb_telemetry_pb2.py +42 -0
  186. wandb/proto/wandb_base_pb2.py +10 -0
  187. wandb/proto/wandb_deprecated.py +53 -0
  188. wandb/proto/wandb_generate_deprecated.py +34 -0
  189. wandb/proto/wandb_generate_proto.py +49 -0
  190. wandb/proto/wandb_internal_pb2.py +16 -0
  191. wandb/proto/wandb_server_pb2.py +10 -0
  192. wandb/proto/wandb_settings_pb2.py +10 -0
  193. wandb/proto/wandb_telemetry_pb2.py +10 -0
  194. wandb/py.typed +0 -0
  195. wandb/sdk/__init__.py +37 -0
  196. wandb/sdk/artifacts/__init__.py +0 -0
  197. wandb/sdk/artifacts/_validators.py +90 -0
  198. wandb/sdk/artifacts/artifact.py +2389 -0
  199. wandb/sdk/artifacts/artifact_download_logger.py +43 -0
  200. wandb/sdk/artifacts/artifact_file_cache.py +253 -0
  201. wandb/sdk/artifacts/artifact_instance_cache.py +17 -0
  202. wandb/sdk/artifacts/artifact_manifest.py +74 -0
  203. wandb/sdk/artifacts/artifact_manifest_entry.py +249 -0
  204. wandb/sdk/artifacts/artifact_manifests/__init__.py +0 -0
  205. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +92 -0
  206. wandb/sdk/artifacts/artifact_saver.py +269 -0
  207. wandb/sdk/artifacts/artifact_state.py +11 -0
  208. wandb/sdk/artifacts/artifact_ttl.py +7 -0
  209. wandb/sdk/artifacts/exceptions.py +57 -0
  210. wandb/sdk/artifacts/staging.py +25 -0
  211. wandb/sdk/artifacts/storage_handler.py +62 -0
  212. wandb/sdk/artifacts/storage_handlers/__init__.py +0 -0
  213. wandb/sdk/artifacts/storage_handlers/azure_handler.py +208 -0
  214. wandb/sdk/artifacts/storage_handlers/gcs_handler.py +228 -0
  215. wandb/sdk/artifacts/storage_handlers/http_handler.py +114 -0
  216. wandb/sdk/artifacts/storage_handlers/local_file_handler.py +141 -0
  217. wandb/sdk/artifacts/storage_handlers/multi_handler.py +56 -0
  218. wandb/sdk/artifacts/storage_handlers/s3_handler.py +300 -0
  219. wandb/sdk/artifacts/storage_handlers/tracking_handler.py +72 -0
  220. wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +135 -0
  221. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +74 -0
  222. wandb/sdk/artifacts/storage_layout.py +6 -0
  223. wandb/sdk/artifacts/storage_policies/__init__.py +4 -0
  224. wandb/sdk/artifacts/storage_policies/register.py +1 -0
  225. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +378 -0
  226. wandb/sdk/artifacts/storage_policy.py +72 -0
  227. wandb/sdk/backend/__init__.py +0 -0
  228. wandb/sdk/backend/backend.py +222 -0
  229. wandb/sdk/data_types/__init__.py +0 -0
  230. wandb/sdk/data_types/_dtypes.py +914 -0
  231. wandb/sdk/data_types/_private.py +10 -0
  232. wandb/sdk/data_types/audio.py +165 -0
  233. wandb/sdk/data_types/base_types/__init__.py +0 -0
  234. wandb/sdk/data_types/base_types/json_metadata.py +55 -0
  235. wandb/sdk/data_types/base_types/media.py +315 -0
  236. wandb/sdk/data_types/base_types/wb_value.py +272 -0
  237. wandb/sdk/data_types/bokeh.py +70 -0
  238. wandb/sdk/data_types/graph.py +405 -0
  239. wandb/sdk/data_types/helper_types/__init__.py +0 -0
  240. wandb/sdk/data_types/helper_types/bounding_boxes_2d.py +295 -0
  241. wandb/sdk/data_types/helper_types/classes.py +159 -0
  242. wandb/sdk/data_types/helper_types/image_mask.py +235 -0
  243. wandb/sdk/data_types/histogram.py +96 -0
  244. wandb/sdk/data_types/html.py +115 -0
  245. wandb/sdk/data_types/image.py +845 -0
  246. wandb/sdk/data_types/molecule.py +241 -0
  247. wandb/sdk/data_types/object_3d.py +474 -0
  248. wandb/sdk/data_types/plotly.py +82 -0
  249. wandb/sdk/data_types/saved_model.py +446 -0
  250. wandb/sdk/data_types/table.py +1204 -0
  251. wandb/sdk/data_types/trace_tree.py +438 -0
  252. wandb/sdk/data_types/utils.py +229 -0
  253. wandb/sdk/data_types/video.py +247 -0
  254. wandb/sdk/integration_utils/__init__.py +0 -0
  255. wandb/sdk/integration_utils/auto_logging.py +239 -0
  256. wandb/sdk/integration_utils/data_logging.py +475 -0
  257. wandb/sdk/interface/__init__.py +0 -0
  258. wandb/sdk/interface/constants.py +4 -0
  259. wandb/sdk/interface/interface.py +972 -0
  260. wandb/sdk/interface/interface_queue.py +59 -0
  261. wandb/sdk/interface/interface_relay.py +53 -0
  262. wandb/sdk/interface/interface_shared.py +537 -0
  263. wandb/sdk/interface/interface_sock.py +61 -0
  264. wandb/sdk/interface/message_future.py +27 -0
  265. wandb/sdk/interface/message_future_poll.py +50 -0
  266. wandb/sdk/interface/router.py +118 -0
  267. wandb/sdk/interface/router_queue.py +44 -0
  268. wandb/sdk/interface/router_relay.py +39 -0
  269. wandb/sdk/interface/router_sock.py +36 -0
  270. wandb/sdk/interface/summary_record.py +67 -0
  271. wandb/sdk/internal/__init__.py +0 -0
  272. wandb/sdk/internal/context.py +89 -0
  273. wandb/sdk/internal/datastore.py +297 -0
  274. wandb/sdk/internal/file_pusher.py +181 -0
  275. wandb/sdk/internal/file_stream.py +695 -0
  276. wandb/sdk/internal/flow_control.py +263 -0
  277. wandb/sdk/internal/handler.py +901 -0
  278. wandb/sdk/internal/internal.py +417 -0
  279. wandb/sdk/internal/internal_api.py +4358 -0
  280. wandb/sdk/internal/internal_util.py +100 -0
  281. wandb/sdk/internal/job_builder.py +629 -0
  282. wandb/sdk/internal/profiler.py +78 -0
  283. wandb/sdk/internal/progress.py +83 -0
  284. wandb/sdk/internal/run.py +25 -0
  285. wandb/sdk/internal/sample.py +70 -0
  286. wandb/sdk/internal/sender.py +1686 -0
  287. wandb/sdk/internal/sender_config.py +197 -0
  288. wandb/sdk/internal/settings_static.py +90 -0
  289. wandb/sdk/internal/system/__init__.py +0 -0
  290. wandb/sdk/internal/system/assets/__init__.py +27 -0
  291. wandb/sdk/internal/system/assets/aggregators.py +37 -0
  292. wandb/sdk/internal/system/assets/asset_registry.py +20 -0
  293. wandb/sdk/internal/system/assets/cpu.py +163 -0
  294. wandb/sdk/internal/system/assets/disk.py +210 -0
  295. wandb/sdk/internal/system/assets/gpu.py +416 -0
  296. wandb/sdk/internal/system/assets/gpu_amd.py +239 -0
  297. wandb/sdk/internal/system/assets/gpu_apple.py +177 -0
  298. wandb/sdk/internal/system/assets/interfaces.py +207 -0
  299. wandb/sdk/internal/system/assets/ipu.py +177 -0
  300. wandb/sdk/internal/system/assets/memory.py +166 -0
  301. wandb/sdk/internal/system/assets/network.py +125 -0
  302. wandb/sdk/internal/system/assets/open_metrics.py +299 -0
  303. wandb/sdk/internal/system/assets/tpu.py +154 -0
  304. wandb/sdk/internal/system/assets/trainium.py +399 -0
  305. wandb/sdk/internal/system/env_probe_helpers.py +13 -0
  306. wandb/sdk/internal/system/system_info.py +249 -0
  307. wandb/sdk/internal/system/system_monitor.py +229 -0
  308. wandb/sdk/internal/tb_watcher.py +518 -0
  309. wandb/sdk/internal/thread_local_settings.py +18 -0
  310. wandb/sdk/internal/writer.py +206 -0
  311. wandb/sdk/launch/__init__.py +14 -0
  312. wandb/sdk/launch/_launch.py +330 -0
  313. wandb/sdk/launch/_launch_add.py +255 -0
  314. wandb/sdk/launch/_project_spec.py +566 -0
  315. wandb/sdk/launch/agent/__init__.py +5 -0
  316. wandb/sdk/launch/agent/agent.py +924 -0
  317. wandb/sdk/launch/agent/config.py +296 -0
  318. wandb/sdk/launch/agent/job_status_tracker.py +53 -0
  319. wandb/sdk/launch/agent/run_queue_item_file_saver.py +45 -0
  320. wandb/sdk/launch/builder/__init__.py +0 -0
  321. wandb/sdk/launch/builder/abstract.py +156 -0
  322. wandb/sdk/launch/builder/build.py +297 -0
  323. wandb/sdk/launch/builder/context_manager.py +235 -0
  324. wandb/sdk/launch/builder/docker_builder.py +177 -0
  325. wandb/sdk/launch/builder/kaniko_builder.py +595 -0
  326. wandb/sdk/launch/builder/noop.py +58 -0
  327. wandb/sdk/launch/builder/templates/_wandb_bootstrap.py +188 -0
  328. wandb/sdk/launch/builder/templates/dockerfile.py +92 -0
  329. wandb/sdk/launch/create_job.py +528 -0
  330. wandb/sdk/launch/environment/abstract.py +29 -0
  331. wandb/sdk/launch/environment/aws_environment.py +322 -0
  332. wandb/sdk/launch/environment/azure_environment.py +105 -0
  333. wandb/sdk/launch/environment/gcp_environment.py +335 -0
  334. wandb/sdk/launch/environment/local_environment.py +66 -0
  335. wandb/sdk/launch/errors.py +19 -0
  336. wandb/sdk/launch/git_reference.py +109 -0
  337. wandb/sdk/launch/inputs/files.py +148 -0
  338. wandb/sdk/launch/inputs/internal.py +315 -0
  339. wandb/sdk/launch/inputs/manage.py +113 -0
  340. wandb/sdk/launch/inputs/schema.py +39 -0
  341. wandb/sdk/launch/loader.py +249 -0
  342. wandb/sdk/launch/registry/abstract.py +48 -0
  343. wandb/sdk/launch/registry/anon.py +29 -0
  344. wandb/sdk/launch/registry/azure_container_registry.py +124 -0
  345. wandb/sdk/launch/registry/elastic_container_registry.py +192 -0
  346. wandb/sdk/launch/registry/google_artifact_registry.py +219 -0
  347. wandb/sdk/launch/registry/local_registry.py +67 -0
  348. wandb/sdk/launch/runner/__init__.py +0 -0
  349. wandb/sdk/launch/runner/abstract.py +195 -0
  350. wandb/sdk/launch/runner/kubernetes_monitor.py +474 -0
  351. wandb/sdk/launch/runner/kubernetes_runner.py +963 -0
  352. wandb/sdk/launch/runner/local_container.py +301 -0
  353. wandb/sdk/launch/runner/local_process.py +78 -0
  354. wandb/sdk/launch/runner/sagemaker_runner.py +426 -0
  355. wandb/sdk/launch/runner/vertex_runner.py +230 -0
  356. wandb/sdk/launch/sweeps/__init__.py +39 -0
  357. wandb/sdk/launch/sweeps/scheduler.py +742 -0
  358. wandb/sdk/launch/sweeps/scheduler_sweep.py +91 -0
  359. wandb/sdk/launch/sweeps/utils.py +316 -0
  360. wandb/sdk/launch/utils.py +746 -0
  361. wandb/sdk/launch/wandb_reference.py +138 -0
  362. wandb/sdk/lib/__init__.py +5 -0
  363. wandb/sdk/lib/_settings_toposort_generate.py +159 -0
  364. wandb/sdk/lib/_settings_toposort_generated.py +250 -0
  365. wandb/sdk/lib/_wburls_generate.py +25 -0
  366. wandb/sdk/lib/_wburls_generated.py +22 -0
  367. wandb/sdk/lib/apikey.py +273 -0
  368. wandb/sdk/lib/capped_dict.py +26 -0
  369. wandb/sdk/lib/config_util.py +101 -0
  370. wandb/sdk/lib/credentials.py +141 -0
  371. wandb/sdk/lib/deprecate.py +42 -0
  372. wandb/sdk/lib/disabled.py +29 -0
  373. wandb/sdk/lib/exit_hooks.py +54 -0
  374. wandb/sdk/lib/file_stream_utils.py +118 -0
  375. wandb/sdk/lib/filenames.py +64 -0
  376. wandb/sdk/lib/filesystem.py +372 -0
  377. wandb/sdk/lib/fsm.py +174 -0
  378. wandb/sdk/lib/gitlib.py +239 -0
  379. wandb/sdk/lib/gql_request.py +65 -0
  380. wandb/sdk/lib/handler_util.py +21 -0
  381. wandb/sdk/lib/hashutil.py +84 -0
  382. wandb/sdk/lib/import_hooks.py +275 -0
  383. wandb/sdk/lib/ipython.py +146 -0
  384. wandb/sdk/lib/json_util.py +80 -0
  385. wandb/sdk/lib/lazyloader.py +63 -0
  386. wandb/sdk/lib/mailbox.py +460 -0
  387. wandb/sdk/lib/module.py +69 -0
  388. wandb/sdk/lib/paths.py +106 -0
  389. wandb/sdk/lib/preinit.py +42 -0
  390. wandb/sdk/lib/printer.py +313 -0
  391. wandb/sdk/lib/proto_util.py +90 -0
  392. wandb/sdk/lib/redirect.py +845 -0
  393. wandb/sdk/lib/reporting.py +99 -0
  394. wandb/sdk/lib/retry.py +289 -0
  395. wandb/sdk/lib/run_moment.py +78 -0
  396. wandb/sdk/lib/runid.py +12 -0
  397. wandb/sdk/lib/server.py +52 -0
  398. wandb/sdk/lib/service_connection.py +216 -0
  399. wandb/sdk/lib/service_token.py +94 -0
  400. wandb/sdk/lib/sock_client.py +295 -0
  401. wandb/sdk/lib/sparkline.py +45 -0
  402. wandb/sdk/lib/telemetry.py +100 -0
  403. wandb/sdk/lib/timed_input.py +133 -0
  404. wandb/sdk/lib/timer.py +19 -0
  405. wandb/sdk/lib/tracelog.py +255 -0
  406. wandb/sdk/lib/wburls.py +46 -0
  407. wandb/sdk/service/__init__.py +0 -0
  408. wandb/sdk/service/_startup_debug.py +22 -0
  409. wandb/sdk/service/port_file.py +53 -0
  410. wandb/sdk/service/server.py +116 -0
  411. wandb/sdk/service/server_sock.py +276 -0
  412. wandb/sdk/service/service.py +242 -0
  413. wandb/sdk/service/streams.py +417 -0
  414. wandb/sdk/verify/__init__.py +0 -0
  415. wandb/sdk/verify/verify.py +501 -0
  416. wandb/sdk/wandb_alerts.py +12 -0
  417. wandb/sdk/wandb_config.py +322 -0
  418. wandb/sdk/wandb_helper.py +54 -0
  419. wandb/sdk/wandb_init.py +1266 -0
  420. wandb/sdk/wandb_login.py +349 -0
  421. wandb/sdk/wandb_metric.py +110 -0
  422. wandb/sdk/wandb_require.py +97 -0
  423. wandb/sdk/wandb_require_helpers.py +44 -0
  424. wandb/sdk/wandb_run.py +4236 -0
  425. wandb/sdk/wandb_settings.py +2001 -0
  426. wandb/sdk/wandb_setup.py +409 -0
  427. wandb/sdk/wandb_summary.py +150 -0
  428. wandb/sdk/wandb_sweep.py +119 -0
  429. wandb/sdk/wandb_sync.py +81 -0
  430. wandb/sdk/wandb_watch.py +144 -0
  431. wandb/sklearn.py +35 -0
  432. wandb/sync/__init__.py +3 -0
  433. wandb/sync/sync.py +443 -0
  434. wandb/trigger.py +29 -0
  435. wandb/util.py +1956 -0
  436. wandb/vendor/__init__.py +0 -0
  437. wandb/vendor/gql-0.2.0/setup.py +40 -0
  438. wandb/vendor/gql-0.2.0/tests/__init__.py +0 -0
  439. wandb/vendor/gql-0.2.0/tests/starwars/__init__.py +0 -0
  440. wandb/vendor/gql-0.2.0/tests/starwars/fixtures.py +96 -0
  441. wandb/vendor/gql-0.2.0/tests/starwars/schema.py +146 -0
  442. wandb/vendor/gql-0.2.0/tests/starwars/test_dsl.py +293 -0
  443. wandb/vendor/gql-0.2.0/tests/starwars/test_query.py +355 -0
  444. wandb/vendor/gql-0.2.0/tests/starwars/test_validation.py +171 -0
  445. wandb/vendor/gql-0.2.0/tests/test_client.py +31 -0
  446. wandb/vendor/gql-0.2.0/tests/test_transport.py +89 -0
  447. wandb/vendor/gql-0.2.0/wandb_gql/__init__.py +4 -0
  448. wandb/vendor/gql-0.2.0/wandb_gql/client.py +75 -0
  449. wandb/vendor/gql-0.2.0/wandb_gql/dsl.py +152 -0
  450. wandb/vendor/gql-0.2.0/wandb_gql/gql.py +10 -0
  451. wandb/vendor/gql-0.2.0/wandb_gql/transport/__init__.py +0 -0
  452. wandb/vendor/gql-0.2.0/wandb_gql/transport/http.py +6 -0
  453. wandb/vendor/gql-0.2.0/wandb_gql/transport/local_schema.py +15 -0
  454. wandb/vendor/gql-0.2.0/wandb_gql/transport/requests.py +46 -0
  455. wandb/vendor/gql-0.2.0/wandb_gql/utils.py +21 -0
  456. wandb/vendor/graphql-core-1.1/setup.py +86 -0
  457. wandb/vendor/graphql-core-1.1/wandb_graphql/__init__.py +287 -0
  458. wandb/vendor/graphql-core-1.1/wandb_graphql/error/__init__.py +6 -0
  459. wandb/vendor/graphql-core-1.1/wandb_graphql/error/base.py +42 -0
  460. wandb/vendor/graphql-core-1.1/wandb_graphql/error/format_error.py +11 -0
  461. wandb/vendor/graphql-core-1.1/wandb_graphql/error/located_error.py +29 -0
  462. wandb/vendor/graphql-core-1.1/wandb_graphql/error/syntax_error.py +36 -0
  463. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/__init__.py +26 -0
  464. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/base.py +311 -0
  465. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executor.py +398 -0
  466. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executors/__init__.py +0 -0
  467. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executors/asyncio.py +53 -0
  468. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executors/gevent.py +22 -0
  469. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executors/process.py +32 -0
  470. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executors/sync.py +7 -0
  471. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executors/thread.py +35 -0
  472. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/executors/utils.py +6 -0
  473. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/experimental/__init__.py +0 -0
  474. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/experimental/executor.py +66 -0
  475. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/experimental/fragment.py +252 -0
  476. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/experimental/resolver.py +151 -0
  477. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/experimental/utils.py +7 -0
  478. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/middleware.py +57 -0
  479. wandb/vendor/graphql-core-1.1/wandb_graphql/execution/values.py +145 -0
  480. wandb/vendor/graphql-core-1.1/wandb_graphql/graphql.py +60 -0
  481. wandb/vendor/graphql-core-1.1/wandb_graphql/language/__init__.py +0 -0
  482. wandb/vendor/graphql-core-1.1/wandb_graphql/language/ast.py +1349 -0
  483. wandb/vendor/graphql-core-1.1/wandb_graphql/language/base.py +19 -0
  484. wandb/vendor/graphql-core-1.1/wandb_graphql/language/lexer.py +435 -0
  485. wandb/vendor/graphql-core-1.1/wandb_graphql/language/location.py +30 -0
  486. wandb/vendor/graphql-core-1.1/wandb_graphql/language/parser.py +779 -0
  487. wandb/vendor/graphql-core-1.1/wandb_graphql/language/printer.py +193 -0
  488. wandb/vendor/graphql-core-1.1/wandb_graphql/language/source.py +18 -0
  489. wandb/vendor/graphql-core-1.1/wandb_graphql/language/visitor.py +222 -0
  490. wandb/vendor/graphql-core-1.1/wandb_graphql/language/visitor_meta.py +82 -0
  491. wandb/vendor/graphql-core-1.1/wandb_graphql/pyutils/__init__.py +0 -0
  492. wandb/vendor/graphql-core-1.1/wandb_graphql/pyutils/cached_property.py +17 -0
  493. wandb/vendor/graphql-core-1.1/wandb_graphql/pyutils/contain_subset.py +28 -0
  494. wandb/vendor/graphql-core-1.1/wandb_graphql/pyutils/default_ordered_dict.py +40 -0
  495. wandb/vendor/graphql-core-1.1/wandb_graphql/pyutils/ordereddict.py +8 -0
  496. wandb/vendor/graphql-core-1.1/wandb_graphql/pyutils/pair_set.py +43 -0
  497. wandb/vendor/graphql-core-1.1/wandb_graphql/pyutils/version.py +78 -0
  498. wandb/vendor/graphql-core-1.1/wandb_graphql/type/__init__.py +67 -0
  499. wandb/vendor/graphql-core-1.1/wandb_graphql/type/definition.py +619 -0
  500. wandb/vendor/graphql-core-1.1/wandb_graphql/type/directives.py +132 -0
  501. wandb/vendor/graphql-core-1.1/wandb_graphql/type/introspection.py +440 -0
  502. wandb/vendor/graphql-core-1.1/wandb_graphql/type/scalars.py +131 -0
  503. wandb/vendor/graphql-core-1.1/wandb_graphql/type/schema.py +100 -0
  504. wandb/vendor/graphql-core-1.1/wandb_graphql/type/typemap.py +145 -0
  505. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/__init__.py +0 -0
  506. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/assert_valid_name.py +9 -0
  507. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/ast_from_value.py +65 -0
  508. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/ast_to_code.py +49 -0
  509. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/ast_to_dict.py +24 -0
  510. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/base.py +75 -0
  511. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/build_ast_schema.py +291 -0
  512. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/build_client_schema.py +250 -0
  513. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/concat_ast.py +9 -0
  514. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/extend_schema.py +357 -0
  515. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/get_field_def.py +27 -0
  516. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/get_operation_ast.py +21 -0
  517. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/introspection_query.py +90 -0
  518. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/is_valid_literal_value.py +67 -0
  519. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/is_valid_value.py +66 -0
  520. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/quoted_or_list.py +21 -0
  521. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/schema_printer.py +168 -0
  522. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/suggestion_list.py +56 -0
  523. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/type_comparators.py +69 -0
  524. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/type_from_ast.py +21 -0
  525. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/type_info.py +149 -0
  526. wandb/vendor/graphql-core-1.1/wandb_graphql/utils/value_from_ast.py +69 -0
  527. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/__init__.py +4 -0
  528. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/__init__.py +79 -0
  529. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/arguments_of_correct_type.py +24 -0
  530. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/base.py +8 -0
  531. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/default_values_of_correct_type.py +44 -0
  532. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/fields_on_correct_type.py +113 -0
  533. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/fragments_on_composite_types.py +33 -0
  534. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/known_argument_names.py +70 -0
  535. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/known_directives.py +97 -0
  536. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/known_fragment_names.py +19 -0
  537. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/known_type_names.py +43 -0
  538. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/lone_anonymous_operation.py +23 -0
  539. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/no_fragment_cycles.py +59 -0
  540. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/no_undefined_variables.py +36 -0
  541. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/no_unused_fragments.py +38 -0
  542. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/no_unused_variables.py +37 -0
  543. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/overlapping_fields_can_be_merged.py +529 -0
  544. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/possible_fragment_spreads.py +44 -0
  545. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/provided_non_null_arguments.py +46 -0
  546. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/scalar_leafs.py +33 -0
  547. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/unique_argument_names.py +32 -0
  548. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/unique_fragment_names.py +28 -0
  549. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/unique_input_field_names.py +33 -0
  550. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/unique_operation_names.py +31 -0
  551. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/unique_variable_names.py +27 -0
  552. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/variables_are_input_types.py +21 -0
  553. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/rules/variables_in_allowed_position.py +53 -0
  554. wandb/vendor/graphql-core-1.1/wandb_graphql/validation/validation.py +158 -0
  555. wandb/vendor/promise-2.3.0/conftest.py +30 -0
  556. wandb/vendor/promise-2.3.0/setup.py +64 -0
  557. wandb/vendor/promise-2.3.0/tests/__init__.py +0 -0
  558. wandb/vendor/promise-2.3.0/tests/conftest.py +8 -0
  559. wandb/vendor/promise-2.3.0/tests/test_awaitable.py +32 -0
  560. wandb/vendor/promise-2.3.0/tests/test_awaitable_35.py +47 -0
  561. wandb/vendor/promise-2.3.0/tests/test_benchmark.py +116 -0
  562. wandb/vendor/promise-2.3.0/tests/test_complex_threads.py +23 -0
  563. wandb/vendor/promise-2.3.0/tests/test_dataloader.py +452 -0
  564. wandb/vendor/promise-2.3.0/tests/test_dataloader_awaitable_35.py +99 -0
  565. wandb/vendor/promise-2.3.0/tests/test_dataloader_extra.py +65 -0
  566. wandb/vendor/promise-2.3.0/tests/test_extra.py +670 -0
  567. wandb/vendor/promise-2.3.0/tests/test_issues.py +132 -0
  568. wandb/vendor/promise-2.3.0/tests/test_promise_list.py +70 -0
  569. wandb/vendor/promise-2.3.0/tests/test_spec.py +584 -0
  570. wandb/vendor/promise-2.3.0/tests/test_thread_safety.py +115 -0
  571. wandb/vendor/promise-2.3.0/tests/utils.py +3 -0
  572. wandb/vendor/promise-2.3.0/wandb_promise/__init__.py +38 -0
  573. wandb/vendor/promise-2.3.0/wandb_promise/async_.py +135 -0
  574. wandb/vendor/promise-2.3.0/wandb_promise/compat.py +32 -0
  575. wandb/vendor/promise-2.3.0/wandb_promise/dataloader.py +326 -0
  576. wandb/vendor/promise-2.3.0/wandb_promise/iterate_promise.py +12 -0
  577. wandb/vendor/promise-2.3.0/wandb_promise/promise.py +848 -0
  578. wandb/vendor/promise-2.3.0/wandb_promise/promise_list.py +151 -0
  579. wandb/vendor/promise-2.3.0/wandb_promise/pyutils/__init__.py +0 -0
  580. wandb/vendor/promise-2.3.0/wandb_promise/pyutils/version.py +83 -0
  581. wandb/vendor/promise-2.3.0/wandb_promise/schedulers/__init__.py +0 -0
  582. wandb/vendor/promise-2.3.0/wandb_promise/schedulers/asyncio.py +22 -0
  583. wandb/vendor/promise-2.3.0/wandb_promise/schedulers/gevent.py +21 -0
  584. wandb/vendor/promise-2.3.0/wandb_promise/schedulers/immediate.py +27 -0
  585. wandb/vendor/promise-2.3.0/wandb_promise/schedulers/thread.py +18 -0
  586. wandb/vendor/promise-2.3.0/wandb_promise/utils.py +56 -0
  587. wandb/vendor/pygments/__init__.py +90 -0
  588. wandb/vendor/pygments/cmdline.py +568 -0
  589. wandb/vendor/pygments/console.py +74 -0
  590. wandb/vendor/pygments/filter.py +74 -0
  591. wandb/vendor/pygments/filters/__init__.py +350 -0
  592. wandb/vendor/pygments/formatter.py +95 -0
  593. wandb/vendor/pygments/formatters/__init__.py +153 -0
  594. wandb/vendor/pygments/formatters/_mapping.py +85 -0
  595. wandb/vendor/pygments/formatters/bbcode.py +109 -0
  596. wandb/vendor/pygments/formatters/html.py +851 -0
  597. wandb/vendor/pygments/formatters/img.py +600 -0
  598. wandb/vendor/pygments/formatters/irc.py +182 -0
  599. wandb/vendor/pygments/formatters/latex.py +482 -0
  600. wandb/vendor/pygments/formatters/other.py +160 -0
  601. wandb/vendor/pygments/formatters/rtf.py +147 -0
  602. wandb/vendor/pygments/formatters/svg.py +153 -0
  603. wandb/vendor/pygments/formatters/terminal.py +136 -0
  604. wandb/vendor/pygments/formatters/terminal256.py +309 -0
  605. wandb/vendor/pygments/lexer.py +871 -0
  606. wandb/vendor/pygments/lexers/__init__.py +329 -0
  607. wandb/vendor/pygments/lexers/_asy_builtins.py +1645 -0
  608. wandb/vendor/pygments/lexers/_cl_builtins.py +232 -0
  609. wandb/vendor/pygments/lexers/_cocoa_builtins.py +72 -0
  610. wandb/vendor/pygments/lexers/_csound_builtins.py +1346 -0
  611. wandb/vendor/pygments/lexers/_lasso_builtins.py +5327 -0
  612. wandb/vendor/pygments/lexers/_lua_builtins.py +295 -0
  613. wandb/vendor/pygments/lexers/_mapping.py +500 -0
  614. wandb/vendor/pygments/lexers/_mql_builtins.py +1172 -0
  615. wandb/vendor/pygments/lexers/_openedge_builtins.py +2547 -0
  616. wandb/vendor/pygments/lexers/_php_builtins.py +4756 -0
  617. wandb/vendor/pygments/lexers/_postgres_builtins.py +621 -0
  618. wandb/vendor/pygments/lexers/_scilab_builtins.py +3094 -0
  619. wandb/vendor/pygments/lexers/_sourcemod_builtins.py +1163 -0
  620. wandb/vendor/pygments/lexers/_stan_builtins.py +532 -0
  621. wandb/vendor/pygments/lexers/_stata_builtins.py +419 -0
  622. wandb/vendor/pygments/lexers/_tsql_builtins.py +1004 -0
  623. wandb/vendor/pygments/lexers/_vim_builtins.py +1939 -0
  624. wandb/vendor/pygments/lexers/actionscript.py +240 -0
  625. wandb/vendor/pygments/lexers/agile.py +24 -0
  626. wandb/vendor/pygments/lexers/algebra.py +221 -0
  627. wandb/vendor/pygments/lexers/ambient.py +76 -0
  628. wandb/vendor/pygments/lexers/ampl.py +87 -0
  629. wandb/vendor/pygments/lexers/apl.py +101 -0
  630. wandb/vendor/pygments/lexers/archetype.py +318 -0
  631. wandb/vendor/pygments/lexers/asm.py +641 -0
  632. wandb/vendor/pygments/lexers/automation.py +374 -0
  633. wandb/vendor/pygments/lexers/basic.py +500 -0
  634. wandb/vendor/pygments/lexers/bibtex.py +160 -0
  635. wandb/vendor/pygments/lexers/business.py +612 -0
  636. wandb/vendor/pygments/lexers/c_cpp.py +252 -0
  637. wandb/vendor/pygments/lexers/c_like.py +541 -0
  638. wandb/vendor/pygments/lexers/capnproto.py +78 -0
  639. wandb/vendor/pygments/lexers/chapel.py +102 -0
  640. wandb/vendor/pygments/lexers/clean.py +288 -0
  641. wandb/vendor/pygments/lexers/compiled.py +34 -0
  642. wandb/vendor/pygments/lexers/configs.py +833 -0
  643. wandb/vendor/pygments/lexers/console.py +114 -0
  644. wandb/vendor/pygments/lexers/crystal.py +393 -0
  645. wandb/vendor/pygments/lexers/csound.py +366 -0
  646. wandb/vendor/pygments/lexers/css.py +689 -0
  647. wandb/vendor/pygments/lexers/d.py +251 -0
  648. wandb/vendor/pygments/lexers/dalvik.py +125 -0
  649. wandb/vendor/pygments/lexers/data.py +555 -0
  650. wandb/vendor/pygments/lexers/diff.py +165 -0
  651. wandb/vendor/pygments/lexers/dotnet.py +691 -0
  652. wandb/vendor/pygments/lexers/dsls.py +878 -0
  653. wandb/vendor/pygments/lexers/dylan.py +289 -0
  654. wandb/vendor/pygments/lexers/ecl.py +125 -0
  655. wandb/vendor/pygments/lexers/eiffel.py +65 -0
  656. wandb/vendor/pygments/lexers/elm.py +121 -0
  657. wandb/vendor/pygments/lexers/erlang.py +533 -0
  658. wandb/vendor/pygments/lexers/esoteric.py +277 -0
  659. wandb/vendor/pygments/lexers/ezhil.py +69 -0
  660. wandb/vendor/pygments/lexers/factor.py +344 -0
  661. wandb/vendor/pygments/lexers/fantom.py +250 -0
  662. wandb/vendor/pygments/lexers/felix.py +273 -0
  663. wandb/vendor/pygments/lexers/forth.py +177 -0
  664. wandb/vendor/pygments/lexers/fortran.py +205 -0
  665. wandb/vendor/pygments/lexers/foxpro.py +428 -0
  666. wandb/vendor/pygments/lexers/functional.py +21 -0
  667. wandb/vendor/pygments/lexers/go.py +101 -0
  668. wandb/vendor/pygments/lexers/grammar_notation.py +213 -0
  669. wandb/vendor/pygments/lexers/graph.py +80 -0
  670. wandb/vendor/pygments/lexers/graphics.py +553 -0
  671. wandb/vendor/pygments/lexers/haskell.py +843 -0
  672. wandb/vendor/pygments/lexers/haxe.py +936 -0
  673. wandb/vendor/pygments/lexers/hdl.py +382 -0
  674. wandb/vendor/pygments/lexers/hexdump.py +103 -0
  675. wandb/vendor/pygments/lexers/html.py +602 -0
  676. wandb/vendor/pygments/lexers/idl.py +270 -0
  677. wandb/vendor/pygments/lexers/igor.py +288 -0
  678. wandb/vendor/pygments/lexers/inferno.py +96 -0
  679. wandb/vendor/pygments/lexers/installers.py +322 -0
  680. wandb/vendor/pygments/lexers/int_fiction.py +1343 -0
  681. wandb/vendor/pygments/lexers/iolang.py +63 -0
  682. wandb/vendor/pygments/lexers/j.py +146 -0
  683. wandb/vendor/pygments/lexers/javascript.py +1525 -0
  684. wandb/vendor/pygments/lexers/julia.py +333 -0
  685. wandb/vendor/pygments/lexers/jvm.py +1573 -0
  686. wandb/vendor/pygments/lexers/lisp.py +2621 -0
  687. wandb/vendor/pygments/lexers/make.py +202 -0
  688. wandb/vendor/pygments/lexers/markup.py +595 -0
  689. wandb/vendor/pygments/lexers/math.py +21 -0
  690. wandb/vendor/pygments/lexers/matlab.py +663 -0
  691. wandb/vendor/pygments/lexers/ml.py +769 -0
  692. wandb/vendor/pygments/lexers/modeling.py +358 -0
  693. wandb/vendor/pygments/lexers/modula2.py +1561 -0
  694. wandb/vendor/pygments/lexers/monte.py +204 -0
  695. wandb/vendor/pygments/lexers/ncl.py +894 -0
  696. wandb/vendor/pygments/lexers/nimrod.py +159 -0
  697. wandb/vendor/pygments/lexers/nit.py +64 -0
  698. wandb/vendor/pygments/lexers/nix.py +136 -0
  699. wandb/vendor/pygments/lexers/oberon.py +105 -0
  700. wandb/vendor/pygments/lexers/objective.py +504 -0
  701. wandb/vendor/pygments/lexers/ooc.py +85 -0
  702. wandb/vendor/pygments/lexers/other.py +41 -0
  703. wandb/vendor/pygments/lexers/parasail.py +79 -0
  704. wandb/vendor/pygments/lexers/parsers.py +835 -0
  705. wandb/vendor/pygments/lexers/pascal.py +644 -0
  706. wandb/vendor/pygments/lexers/pawn.py +199 -0
  707. wandb/vendor/pygments/lexers/perl.py +620 -0
  708. wandb/vendor/pygments/lexers/php.py +267 -0
  709. wandb/vendor/pygments/lexers/praat.py +294 -0
  710. wandb/vendor/pygments/lexers/prolog.py +306 -0
  711. wandb/vendor/pygments/lexers/python.py +939 -0
  712. wandb/vendor/pygments/lexers/qvt.py +152 -0
  713. wandb/vendor/pygments/lexers/r.py +453 -0
  714. wandb/vendor/pygments/lexers/rdf.py +270 -0
  715. wandb/vendor/pygments/lexers/rebol.py +431 -0
  716. wandb/vendor/pygments/lexers/resource.py +85 -0
  717. wandb/vendor/pygments/lexers/rnc.py +67 -0
  718. wandb/vendor/pygments/lexers/roboconf.py +82 -0
  719. wandb/vendor/pygments/lexers/robotframework.py +560 -0
  720. wandb/vendor/pygments/lexers/ruby.py +519 -0
  721. wandb/vendor/pygments/lexers/rust.py +220 -0
  722. wandb/vendor/pygments/lexers/sas.py +228 -0
  723. wandb/vendor/pygments/lexers/scripting.py +1222 -0
  724. wandb/vendor/pygments/lexers/shell.py +794 -0
  725. wandb/vendor/pygments/lexers/smalltalk.py +195 -0
  726. wandb/vendor/pygments/lexers/smv.py +79 -0
  727. wandb/vendor/pygments/lexers/snobol.py +83 -0
  728. wandb/vendor/pygments/lexers/special.py +103 -0
  729. wandb/vendor/pygments/lexers/sql.py +681 -0
  730. wandb/vendor/pygments/lexers/stata.py +108 -0
  731. wandb/vendor/pygments/lexers/supercollider.py +90 -0
  732. wandb/vendor/pygments/lexers/tcl.py +145 -0
  733. wandb/vendor/pygments/lexers/templates.py +2283 -0
  734. wandb/vendor/pygments/lexers/testing.py +207 -0
  735. wandb/vendor/pygments/lexers/text.py +25 -0
  736. wandb/vendor/pygments/lexers/textedit.py +169 -0
  737. wandb/vendor/pygments/lexers/textfmts.py +297 -0
  738. wandb/vendor/pygments/lexers/theorem.py +458 -0
  739. wandb/vendor/pygments/lexers/trafficscript.py +54 -0
  740. wandb/vendor/pygments/lexers/typoscript.py +226 -0
  741. wandb/vendor/pygments/lexers/urbi.py +133 -0
  742. wandb/vendor/pygments/lexers/varnish.py +190 -0
  743. wandb/vendor/pygments/lexers/verification.py +111 -0
  744. wandb/vendor/pygments/lexers/web.py +24 -0
  745. wandb/vendor/pygments/lexers/webmisc.py +988 -0
  746. wandb/vendor/pygments/lexers/whiley.py +116 -0
  747. wandb/vendor/pygments/lexers/x10.py +69 -0
  748. wandb/vendor/pygments/modeline.py +44 -0
  749. wandb/vendor/pygments/plugin.py +68 -0
  750. wandb/vendor/pygments/regexopt.py +92 -0
  751. wandb/vendor/pygments/scanner.py +105 -0
  752. wandb/vendor/pygments/sphinxext.py +158 -0
  753. wandb/vendor/pygments/style.py +155 -0
  754. wandb/vendor/pygments/styles/__init__.py +80 -0
  755. wandb/vendor/pygments/styles/abap.py +29 -0
  756. wandb/vendor/pygments/styles/algol.py +63 -0
  757. wandb/vendor/pygments/styles/algol_nu.py +63 -0
  758. wandb/vendor/pygments/styles/arduino.py +98 -0
  759. wandb/vendor/pygments/styles/autumn.py +65 -0
  760. wandb/vendor/pygments/styles/borland.py +51 -0
  761. wandb/vendor/pygments/styles/bw.py +49 -0
  762. wandb/vendor/pygments/styles/colorful.py +81 -0
  763. wandb/vendor/pygments/styles/default.py +73 -0
  764. wandb/vendor/pygments/styles/emacs.py +72 -0
  765. wandb/vendor/pygments/styles/friendly.py +72 -0
  766. wandb/vendor/pygments/styles/fruity.py +42 -0
  767. wandb/vendor/pygments/styles/igor.py +29 -0
  768. wandb/vendor/pygments/styles/lovelace.py +97 -0
  769. wandb/vendor/pygments/styles/manni.py +75 -0
  770. wandb/vendor/pygments/styles/monokai.py +106 -0
  771. wandb/vendor/pygments/styles/murphy.py +80 -0
  772. wandb/vendor/pygments/styles/native.py +65 -0
  773. wandb/vendor/pygments/styles/paraiso_dark.py +125 -0
  774. wandb/vendor/pygments/styles/paraiso_light.py +125 -0
  775. wandb/vendor/pygments/styles/pastie.py +75 -0
  776. wandb/vendor/pygments/styles/perldoc.py +69 -0
  777. wandb/vendor/pygments/styles/rainbow_dash.py +89 -0
  778. wandb/vendor/pygments/styles/rrt.py +33 -0
  779. wandb/vendor/pygments/styles/sas.py +44 -0
  780. wandb/vendor/pygments/styles/stata.py +40 -0
  781. wandb/vendor/pygments/styles/tango.py +141 -0
  782. wandb/vendor/pygments/styles/trac.py +63 -0
  783. wandb/vendor/pygments/styles/vim.py +63 -0
  784. wandb/vendor/pygments/styles/vs.py +38 -0
  785. wandb/vendor/pygments/styles/xcode.py +51 -0
  786. wandb/vendor/pygments/token.py +213 -0
  787. wandb/vendor/pygments/unistring.py +217 -0
  788. wandb/vendor/pygments/util.py +388 -0
  789. wandb/vendor/pynvml/__init__.py +0 -0
  790. wandb/vendor/pynvml/pynvml.py +4779 -0
  791. wandb/vendor/watchdog_0_9_0/wandb_watchdog/__init__.py +17 -0
  792. wandb/vendor/watchdog_0_9_0/wandb_watchdog/events.py +615 -0
  793. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/__init__.py +98 -0
  794. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/api.py +369 -0
  795. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/fsevents.py +172 -0
  796. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/fsevents2.py +239 -0
  797. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/inotify.py +218 -0
  798. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/inotify_buffer.py +81 -0
  799. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/inotify_c.py +575 -0
  800. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/kqueue.py +730 -0
  801. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/polling.py +145 -0
  802. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/read_directory_changes.py +133 -0
  803. wandb/vendor/watchdog_0_9_0/wandb_watchdog/observers/winapi.py +348 -0
  804. wandb/vendor/watchdog_0_9_0/wandb_watchdog/patterns.py +265 -0
  805. wandb/vendor/watchdog_0_9_0/wandb_watchdog/tricks/__init__.py +174 -0
  806. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/__init__.py +151 -0
  807. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/bricks.py +249 -0
  808. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/compat.py +29 -0
  809. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/decorators.py +198 -0
  810. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/delayed_queue.py +88 -0
  811. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/dirsnapshot.py +293 -0
  812. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/echo.py +157 -0
  813. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/event_backport.py +41 -0
  814. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/importlib2.py +40 -0
  815. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/platform.py +57 -0
  816. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/unicode_paths.py +64 -0
  817. wandb/vendor/watchdog_0_9_0/wandb_watchdog/utils/win32stat.py +123 -0
  818. wandb/vendor/watchdog_0_9_0/wandb_watchdog/version.py +28 -0
  819. wandb/vendor/watchdog_0_9_0/wandb_watchdog/watchmedo.py +577 -0
  820. wandb/wandb_agent.py +588 -0
  821. wandb/wandb_controller.py +721 -0
  822. wandb/wandb_run.py +9 -0
  823. wandb-0.18.2.dist-info/METADATA +213 -0
  824. wandb-0.18.2.dist-info/RECORD +827 -0
  825. wandb-0.18.2.dist-info/WHEEL +5 -0
  826. wandb-0.18.2.dist-info/entry_points.txt +3 -0
  827. wandb-0.18.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,2389 @@
1
+ """Artifact class."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import atexit
6
+ import concurrent.futures
7
+ import contextlib
8
+ import json
9
+ import logging
10
+ import multiprocessing.dummy
11
+ import os
12
+ import re
13
+ import shutil
14
+ import stat
15
+ import sys
16
+ import tempfile
17
+ import time
18
+ from copy import copy
19
+ from datetime import datetime, timedelta
20
+ from functools import partial
21
+ from pathlib import PurePosixPath
22
+ from typing import IO, TYPE_CHECKING, Any, Dict, Iterator, Sequence, Type, cast
23
+
24
+ from wandb.sdk.artifacts.storage_handlers.gcs_handler import _GCSIsADirectoryError
25
+
26
+ if sys.version_info < (3, 8):
27
+ from typing_extensions import Literal
28
+ else:
29
+ from typing import Literal
30
+
31
+ from urllib.parse import urlparse
32
+
33
+ import requests
34
+
35
+ import wandb
36
+ from wandb import data_types, env, util
37
+ from wandb.apis.normalize import normalize_exceptions
38
+ from wandb.apis.public import ArtifactCollection, ArtifactFiles, RetryingClient, Run
39
+ from wandb.data_types import WBValue
40
+ from wandb.errors.term import termerror, termlog, termwarn
41
+ from wandb.sdk.artifacts._validators import (
42
+ ensure_logged,
43
+ ensure_not_finalized,
44
+ validate_aliases,
45
+ validate_tags,
46
+ )
47
+ from wandb.sdk.artifacts.artifact_download_logger import ArtifactDownloadLogger
48
+ from wandb.sdk.artifacts.artifact_instance_cache import artifact_instance_cache
49
+ from wandb.sdk.artifacts.artifact_manifest import ArtifactManifest
50
+ from wandb.sdk.artifacts.artifact_manifest_entry import ArtifactManifestEntry
51
+ from wandb.sdk.artifacts.artifact_manifests.artifact_manifest_v1 import (
52
+ ArtifactManifestV1,
53
+ )
54
+ from wandb.sdk.artifacts.artifact_state import ArtifactState
55
+ from wandb.sdk.artifacts.artifact_ttl import ArtifactTTL
56
+ from wandb.sdk.artifacts.exceptions import ArtifactNotLoggedError, WaitTimeoutError
57
+ from wandb.sdk.artifacts.staging import get_staging_dir
58
+ from wandb.sdk.artifacts.storage_layout import StorageLayout
59
+ from wandb.sdk.artifacts.storage_policies import WANDB_STORAGE_POLICY
60
+ from wandb.sdk.artifacts.storage_policy import StoragePolicy
61
+ from wandb.sdk.data_types._dtypes import Type as WBType
62
+ from wandb.sdk.data_types._dtypes import TypeRegistry
63
+ from wandb.sdk.internal.internal_api import Api as InternalApi
64
+ from wandb.sdk.internal.thread_local_settings import _thread_local_api_settings
65
+ from wandb.sdk.lib import filesystem, retry, runid, telemetry
66
+ from wandb.sdk.lib.deprecate import Deprecated, deprecate
67
+ from wandb.sdk.lib.hashutil import B64MD5, b64_to_hex_id, md5_file_b64
68
+ from wandb.sdk.lib.mailbox import Mailbox
69
+ from wandb.sdk.lib.paths import FilePathStr, LogicalPath, StrPath, URIStr
70
+ from wandb.sdk.lib.runid import generate_id
71
+
72
+ reset_path = util.vendor_setup()
73
+
74
+ from wandb_gql import gql # noqa: E402
75
+
76
+ reset_path()
77
+
78
+ logger = logging.getLogger(__name__)
79
+
80
+ if TYPE_CHECKING:
81
+ from wandb.sdk.interface.message_future import MessageFuture
82
+
83
+
84
+ class Artifact:
85
+ """Flexible and lightweight building block for dataset and model versioning.
86
+
87
+ Construct an empty W&B Artifact. Populate an artifacts contents with methods that
88
+ begin with `add`. Once the artifact has all the desired files, you can call
89
+ `wandb.log_artifact()` to log it.
90
+
91
+ Arguments:
92
+ name: A human-readable name for the artifact. Use the name to identify
93
+ a specific artifact in the W&B App UI or programmatically. You can
94
+ interactively reference an artifact with the `use_artifact` Public API.
95
+ A name can contain letters, numbers, underscores, hyphens, and dots.
96
+ The name must be unique across a project.
97
+ type: The artifact's type. Use the type of an artifact to both organize
98
+ and differentiate artifacts. You can use any string that contains letters,
99
+ numbers, underscores, hyphens, and dots. Common types include `dataset` or `model`.
100
+ Include `model` within your type string if you want to link the artifact
101
+ to the W&B Model Registry.
102
+ description: A description of the artifact. For Model or Dataset Artifacts,
103
+ add documentation for your standardized team model or dataset card. View
104
+ an artifact's description programmatically with the `Artifact.description`
105
+ attribute or programmatically with the W&B App UI. W&B renders the
106
+ description as markdown in the W&B App.
107
+ metadata: Additional information about an artifact. Specify metadata as a
108
+ dictionary of key-value pairs. You can specify no more than 100 total keys.
109
+
110
+ Returns:
111
+ An `Artifact` object.
112
+ """
113
+
114
+ _TMP_DIR = tempfile.TemporaryDirectory("wandb-artifacts")
115
+ atexit.register(_TMP_DIR.cleanup)
116
+
117
+ def __init__(
118
+ self,
119
+ name: str,
120
+ type: str,
121
+ description: str | None = None,
122
+ metadata: dict[str, Any] | None = None,
123
+ incremental: bool = False,
124
+ use_as: str | None = None,
125
+ ) -> None:
126
+ if not re.match(r"^[a-zA-Z0-9_\-.]+$", name):
127
+ raise ValueError(
128
+ f"Artifact name may only contain alphanumeric characters, dashes, "
129
+ f"underscores, and dots. Invalid name: {name}"
130
+ )
131
+ if type == "job" or type.startswith("wandb-"):
132
+ raise ValueError(
133
+ "Artifact types 'job' and 'wandb-*' are reserved for internal use. "
134
+ "Please use a different type."
135
+ )
136
+ if incremental:
137
+ termwarn("Using experimental arg `incremental`")
138
+
139
+ # Internal.
140
+ self._client: RetryingClient | None = None
141
+
142
+ storage_policy_cls = StoragePolicy.lookup_by_name(WANDB_STORAGE_POLICY)
143
+ layout = StorageLayout.V1 if env.get_use_v1_artifacts() else StorageLayout.V2
144
+ policy_config = {"storageLayout": layout}
145
+ self._storage_policy = storage_policy_cls.from_config(config=policy_config)
146
+
147
+ self._tmp_dir: tempfile.TemporaryDirectory | None = None
148
+ self._added_objs: dict[int, tuple[WBValue, ArtifactManifestEntry]] = {}
149
+ self._added_local_paths: dict[str, ArtifactManifestEntry] = {}
150
+ self._save_future: MessageFuture | None = None
151
+ self._download_roots: set[str] = set()
152
+ # Set by new_draft(), otherwise the latest artifact will be used as the base.
153
+ self._base_id: str | None = None
154
+ # Properties.
155
+ self._id: str | None = None
156
+ self._client_id: str = runid.generate_id(128)
157
+ self._sequence_client_id: str = runid.generate_id(128)
158
+ self._entity: str | None = None
159
+ self._project: str | None = None
160
+ self._name: str = name # includes version after saving
161
+ self._version: str | None = None
162
+ self._source_entity: str | None = None
163
+ self._source_project: str | None = None
164
+ self._source_name: str = name # includes version after saving
165
+ self._source_version: str | None = None
166
+ self._type: str = type
167
+ self._description: str | None = description
168
+ self._metadata: dict = self._normalize_metadata(metadata)
169
+ self._ttl_duration_seconds: int | None = None
170
+ self._ttl_is_inherited: bool = True
171
+ self._ttl_changed: bool = False
172
+ self._aliases: list[str] = []
173
+ self._saved_aliases: list[str] = []
174
+ self._tags: list[str] = []
175
+ self._saved_tags: list[str] = []
176
+ self._distributed_id: str | None = None
177
+ self._incremental: bool = incremental
178
+ self._use_as: str | None = use_as
179
+ self._state: ArtifactState = ArtifactState.PENDING
180
+ self._manifest: ArtifactManifest | None = ArtifactManifestV1(
181
+ self._storage_policy
182
+ )
183
+ self._commit_hash: str | None = None
184
+ self._file_count: int | None = None
185
+ self._created_at: str | None = None
186
+ self._updated_at: str | None = None
187
+ self._final: bool = False
188
+
189
+ # Cache.
190
+ artifact_instance_cache[self._client_id] = self
191
+
192
+ def __repr__(self) -> str:
193
+ return f"<Artifact {self.id or self.name}>"
194
+
195
+ @classmethod
196
+ def _from_id(cls, artifact_id: str, client: RetryingClient) -> Artifact | None:
197
+ artifact = artifact_instance_cache.get(artifact_id)
198
+ if artifact is not None:
199
+ return artifact
200
+
201
+ query = gql(
202
+ """
203
+ query ArtifactByID($id: ID!) {
204
+ artifact(id: $id) {
205
+ ...ArtifactFragment
206
+ currentManifest {
207
+ file {
208
+ directUrl
209
+ }
210
+ }
211
+ }
212
+ }
213
+ """
214
+ + cls._get_gql_artifact_fragment()
215
+ )
216
+ response = client.execute(
217
+ query,
218
+ variable_values={"id": artifact_id},
219
+ )
220
+ attrs = response.get("artifact")
221
+ if attrs is None:
222
+ return None
223
+ attr_project = attrs["artifactSequence"]["project"]
224
+ entity_name = ""
225
+ project_name = ""
226
+ if attr_project:
227
+ entity_name = attr_project["entityName"]
228
+ project_name = attr_project["name"]
229
+ name = "{}:v{}".format(attrs["artifactSequence"]["name"], attrs["versionIndex"])
230
+ return cls._from_attrs(entity_name, project_name, name, attrs, client)
231
+
232
+ @classmethod
233
+ def _from_name(
234
+ cls, entity: str, project: str, name: str, client: RetryingClient
235
+ ) -> Artifact:
236
+ query = gql(
237
+ """
238
+ query ArtifactByName(
239
+ $entityName: String!,
240
+ $projectName: String!,
241
+ $name: String!
242
+ ) {
243
+ project(name: $projectName, entityName: $entityName) {
244
+ artifact(name: $name) {
245
+ ...ArtifactFragment
246
+ }
247
+ }
248
+ }
249
+ """
250
+ + cls._get_gql_artifact_fragment()
251
+ )
252
+ response = client.execute(
253
+ query,
254
+ variable_values={
255
+ "entityName": entity,
256
+ "projectName": project,
257
+ "name": name,
258
+ },
259
+ )
260
+ project_attrs = response.get("project")
261
+ if not project_attrs:
262
+ raise ValueError(f"project '{project}' not found under entity '{entity}'")
263
+ attrs = project_attrs.get("artifact")
264
+ if not attrs:
265
+ raise ValueError(f"artifact '{name}' not found in '{entity}/{project}'")
266
+ return cls._from_attrs(entity, project, name, attrs, client)
267
+
268
+ @classmethod
269
+ def _from_attrs(
270
+ cls,
271
+ entity: str,
272
+ project: str,
273
+ name: str,
274
+ attrs: dict[str, Any],
275
+ client: RetryingClient,
276
+ ) -> Artifact:
277
+ # Placeholder is required to skip validation.
278
+ artifact = cls("placeholder", type="placeholder")
279
+ artifact._client = client
280
+ artifact._id = attrs["id"]
281
+ artifact._entity = entity
282
+ artifact._project = project
283
+ artifact._name = name
284
+ aliases = [
285
+ alias["alias"]
286
+ for alias in attrs["aliases"]
287
+ if alias["artifactCollection"]
288
+ and alias["artifactCollection"]["project"]
289
+ and alias["artifactCollection"]["project"]["entityName"] == entity
290
+ and alias["artifactCollection"]["project"]["name"] == project
291
+ and alias["artifactCollection"]["name"] == name.split(":")[0]
292
+ ]
293
+ tags = [tag_obj["name"] for tag_obj in attrs.get("tags", [])]
294
+ version_aliases = [
295
+ alias for alias in aliases if util.alias_is_version_index(alias)
296
+ ]
297
+ assert len(version_aliases) == 1
298
+ artifact._version = version_aliases[0]
299
+ attr_project = attrs["artifactSequence"]["project"]
300
+ artifact._source_entity = ""
301
+ artifact._source_project = ""
302
+ if attr_project:
303
+ artifact._source_entity = attr_project["entityName"]
304
+ artifact._source_project = attr_project["name"]
305
+ artifact._source_name = "{}:v{}".format(
306
+ attrs["artifactSequence"]["name"], attrs["versionIndex"]
307
+ )
308
+ artifact._source_version = "v{}".format(attrs["versionIndex"])
309
+ artifact._type = attrs["artifactType"]["name"]
310
+ artifact._description = attrs["description"]
311
+ artifact.metadata = cls._normalize_metadata(
312
+ json.loads(attrs["metadata"] or "{}")
313
+ )
314
+ artifact._ttl_duration_seconds = artifact._ttl_duration_seconds_from_gql(
315
+ attrs.get("ttlDurationSeconds")
316
+ )
317
+ artifact._ttl_is_inherited = (
318
+ True if attrs.get("ttlIsInherited") is None else attrs["ttlIsInherited"]
319
+ )
320
+ artifact._aliases = [
321
+ alias for alias in aliases if not util.alias_is_version_index(alias)
322
+ ]
323
+ artifact._saved_aliases = copy(artifact._aliases)
324
+ artifact._tags = tags
325
+ artifact._saved_tags = copy(artifact._tags)
326
+ artifact._state = ArtifactState(attrs["state"])
327
+ if "currentManifest" in attrs:
328
+ artifact._load_manifest(attrs["currentManifest"]["file"]["directUrl"])
329
+ else:
330
+ artifact._manifest = None
331
+ artifact._commit_hash = attrs["commitHash"]
332
+ artifact._file_count = attrs["fileCount"]
333
+ artifact._created_at = attrs["createdAt"]
334
+ artifact._updated_at = attrs["updatedAt"]
335
+ artifact._final = True
336
+ # Cache.
337
+
338
+ assert artifact.id is not None
339
+ artifact_instance_cache[artifact.id] = artifact
340
+ return artifact
341
+
342
+ @ensure_logged
343
+ def new_draft(self) -> Artifact:
344
+ """Create a new draft artifact with the same content as this committed artifact.
345
+
346
+ The artifact returned can be extended or modified and logged as a new version.
347
+
348
+ Returns:
349
+ An `Artifact` object.
350
+
351
+ Raises:
352
+ ArtifactNotLoggedError: If the artifact is not logged.
353
+ """
354
+ # Name, _entity and _project are set to the *source* name/entity/project:
355
+ # if this artifact is saved it must be saved to the source sequence.
356
+ artifact = Artifact(self.source_name.split(":")[0], self.type)
357
+ artifact._entity = self._source_entity
358
+ artifact._project = self._source_project
359
+ artifact._source_entity = self._source_entity
360
+ artifact._source_project = self._source_project
361
+
362
+ # This artifact's parent is the one we are making a draft from.
363
+ artifact._base_id = self.id
364
+
365
+ # We can reuse the client, and copy over all the attributes that aren't
366
+ # version-dependent and don't depend on having been logged.
367
+ artifact._client = self._client
368
+ artifact._description = self.description
369
+ artifact._metadata = self.metadata
370
+ artifact._manifest = ArtifactManifest.from_manifest_json(
371
+ self.manifest.to_manifest_json()
372
+ )
373
+ return artifact
374
+
375
+ # Properties (Python Class managed attributes).
376
+
377
+ @property
378
+ def id(self) -> str | None:
379
+ """The artifact's ID."""
380
+ if self.is_draft():
381
+ return None
382
+ assert self._id is not None
383
+ return self._id
384
+
385
+ @property
386
+ @ensure_logged
387
+ def entity(self) -> str:
388
+ """The name of the entity of the secondary (portfolio) artifact collection."""
389
+ assert self._entity is not None
390
+ return self._entity
391
+
392
+ @property
393
+ @ensure_logged
394
+ def project(self) -> str:
395
+ """The name of the project of the secondary (portfolio) artifact collection."""
396
+ assert self._project is not None
397
+ return self._project
398
+
399
+ @property
400
+ def name(self) -> str:
401
+ """The artifact name and version in its secondary (portfolio) collection.
402
+
403
+ A string with the format {collection}:{alias}. Before the artifact is saved,
404
+ contains only the name since the version is not yet known.
405
+ """
406
+ return self._name
407
+
408
+ @property
409
+ def qualified_name(self) -> str:
410
+ """The entity/project/name of the secondary (portfolio) collection."""
411
+ return f"{self.entity}/{self.project}/{self.name}"
412
+
413
+ @property
414
+ @ensure_logged
415
+ def version(self) -> str:
416
+ """The artifact's version in its secondary (portfolio) collection."""
417
+ assert self._version is not None
418
+ return self._version
419
+
420
+ @property
421
+ @ensure_logged
422
+ def collection(self) -> ArtifactCollection:
423
+ """The collection this artifact was retrieved from.
424
+
425
+ A collection is an ordered group of artifact versions.
426
+ If this artifact was retrieved from a portfolio / linked collection, that
427
+ collection will be returned rather than the collection
428
+ that an artifact version originated from. The collection
429
+ that an artifact originates from is known as the source sequence.
430
+ """
431
+ base_name = self.name.split(":")[0]
432
+ return ArtifactCollection(
433
+ self._client, self.entity, self.project, base_name, self.type
434
+ )
435
+
436
+ @property
437
+ @ensure_logged
438
+ def source_entity(self) -> str:
439
+ """The name of the entity of the primary (sequence) artifact collection."""
440
+ assert self._source_entity is not None
441
+ return self._source_entity
442
+
443
+ @property
444
+ @ensure_logged
445
+ def source_project(self) -> str:
446
+ """The name of the project of the primary (sequence) artifact collection."""
447
+ assert self._source_project is not None
448
+ return self._source_project
449
+
450
+ @property
451
+ def source_name(self) -> str:
452
+ """The artifact name and version in its primary (sequence) collection.
453
+
454
+ A string with the format {collection}:{alias}. Before the artifact is saved,
455
+ contains only the name since the version is not yet known.
456
+ """
457
+ return self._source_name
458
+
459
+ @property
460
+ def source_qualified_name(self) -> str:
461
+ """The entity/project/name of the primary (sequence) collection."""
462
+ return f"{self.source_entity}/{self.source_project}/{self.source_name}"
463
+
464
+ @property
465
+ @ensure_logged
466
+ def source_version(self) -> str:
467
+ """The artifact's version in its primary (sequence) collection.
468
+
469
+ A string with the format "v{number}".
470
+ """
471
+ assert self._source_version is not None
472
+ return self._source_version
473
+
474
+ @property
475
+ @ensure_logged
476
+ def source_collection(self) -> ArtifactCollection:
477
+ """The artifact's primary (sequence) collection."""
478
+ base_name = self.source_name.split(":")[0]
479
+ return ArtifactCollection(
480
+ self._client, self.source_entity, self.source_project, base_name, self.type
481
+ )
482
+
483
+ @property
484
+ def type(self) -> str:
485
+ """The artifact's type. Common types include `dataset` or `model`."""
486
+ return self._type
487
+
488
+ @property
489
+ def description(self) -> str | None:
490
+ """A description of the artifact."""
491
+ return self._description
492
+
493
+ @description.setter
494
+ def description(self, description: str | None) -> None:
495
+ """Set the description of the artifact.
496
+
497
+ For model or dataset Artifacts, add documentation for your
498
+ standardized team model or dataset card. In the W&B UI the
499
+ description is rendered as markdown.
500
+
501
+ Arguments:
502
+ description: Free text that offers a description of the artifact.
503
+ """
504
+ self._description = description
505
+
506
+ @property
507
+ def metadata(self) -> dict:
508
+ """User-defined artifact metadata.
509
+
510
+ Structured data associated with the artifact.
511
+ """
512
+ return self._metadata
513
+
514
+ @metadata.setter
515
+ def metadata(self, metadata: dict) -> None:
516
+ """User-defined artifact metadata.
517
+
518
+ Metadata set this way will eventually be queryable and plottable in the UI; e.g.
519
+ the class distribution of a dataset.
520
+
521
+ Note: There is currently a limit of 100 total keys.
522
+
523
+ Arguments:
524
+ metadata: Structured data associated with the artifact.
525
+ """
526
+ self._metadata = self._normalize_metadata(metadata)
527
+
528
+ @property
529
+ def ttl(self) -> timedelta | None:
530
+ """The time-to-live (TTL) policy of an artifact.
531
+
532
+ Artifacts are deleted shortly after a TTL policy's duration passes.
533
+ If set to `None`, the artifact deactivates TTL policies and will be not
534
+ scheduled for deletion, even if there is a team default TTL.
535
+ An artifact inherits a TTL policy from
536
+ the team default if the team administrator defines a default
537
+ TTL and there is no custom policy set on an artifact.
538
+
539
+ Raises:
540
+ ArtifactNotLoggedError: Unable to fetch inherited TTL if the artifact has not been logged or saved
541
+ """
542
+ if self._ttl_is_inherited and (self.is_draft() or self._ttl_changed):
543
+ raise ArtifactNotLoggedError(f"{type(self).__name__}.ttl", self)
544
+ if self._ttl_duration_seconds is None:
545
+ return None
546
+ return timedelta(seconds=self._ttl_duration_seconds)
547
+
548
+ @ttl.setter
549
+ def ttl(self, ttl: timedelta | ArtifactTTL | None) -> None:
550
+ """The time-to-live (TTL) policy of an artifact.
551
+
552
+ Artifacts are deleted shortly after a TTL policy's duration passes.
553
+ If set to `None`, the artifact has no TTL policy set and it is not
554
+ scheduled for deletion. An artifact inherits a TTL policy from
555
+ the team default if the team administrator defines a default
556
+ TTL and there is no custom policy set on an artifact.
557
+
558
+ Arguments:
559
+ ttl: The duration as a positive Python `datetime.timedelta` Type
560
+ that represents how long the artifact will remain active from its creation.
561
+
562
+ """
563
+ if self.type == "wandb-history":
564
+ raise ValueError("Cannot set artifact TTL for type wandb-history")
565
+
566
+ self._ttl_changed = True
567
+ if isinstance(ttl, ArtifactTTL):
568
+ if ttl == ArtifactTTL.INHERIT:
569
+ self._ttl_is_inherited = True
570
+ else:
571
+ raise ValueError(f"Unhandled ArtifactTTL enum {ttl}")
572
+ else:
573
+ self._ttl_is_inherited = False
574
+ if ttl is None:
575
+ self._ttl_duration_seconds = None
576
+ else:
577
+ if ttl.total_seconds() <= 0:
578
+ raise ValueError(
579
+ f"Artifact TTL Duration has to be positive. ttl: {ttl.total_seconds()}"
580
+ )
581
+ self._ttl_duration_seconds = int(ttl.total_seconds())
582
+
583
+ @property
584
+ @ensure_logged
585
+ def aliases(self) -> list[str]:
586
+ """List of one or more semantically-friendly references or identifying "nicknames" assigned to an artifact version.
587
+
588
+ Aliases are mutable references that you can programmatically reference.
589
+ Change an artifact's alias with the W&B App UI or programmatically.
590
+ See [Create new artifact versions](https://docs.wandb.ai/guides/artifacts/create-a-new-artifact-version)
591
+ for more information.
592
+ """
593
+ return self._aliases
594
+
595
+ @aliases.setter
596
+ @ensure_logged
597
+ def aliases(self, aliases: list[str]) -> None:
598
+ """Set the aliases associated with this artifact."""
599
+ self._aliases = validate_aliases(aliases)
600
+
601
+ @property
602
+ @ensure_logged
603
+ def tags(self) -> list[str]:
604
+ """List of one or more tags assigned to this artifact version."""
605
+ return self._tags
606
+
607
+ @tags.setter
608
+ @ensure_logged
609
+ def tags(self, tags: list[str]) -> None:
610
+ """Set the tags associated with this artifact."""
611
+ self._tags = validate_tags(tags)
612
+
613
+ @property
614
+ def distributed_id(self) -> str | None:
615
+ return self._distributed_id
616
+
617
+ @distributed_id.setter
618
+ def distributed_id(self, distributed_id: str | None) -> None:
619
+ self._distributed_id = distributed_id
620
+
621
+ @property
622
+ def incremental(self) -> bool:
623
+ return self._incremental
624
+
625
+ @property
626
+ def use_as(self) -> str | None:
627
+ return self._use_as
628
+
629
+ @property
630
+ def state(self) -> str:
631
+ """The status of the artifact. One of: "PENDING", "COMMITTED", or "DELETED"."""
632
+ return self._state.value
633
+
634
+ @property
635
+ def manifest(self) -> ArtifactManifest:
636
+ """The artifact's manifest.
637
+
638
+ The manifest lists all of its contents, and can't be changed once the artifact
639
+ has been logged.
640
+ """
641
+ if self._manifest is None:
642
+ query = gql(
643
+ """
644
+ query ArtifactManifest(
645
+ $entityName: String!,
646
+ $projectName: String!,
647
+ $name: String!
648
+ ) {
649
+ project(entityName: $entityName, name: $projectName) {
650
+ artifact(name: $name) {
651
+ currentManifest {
652
+ file {
653
+ directUrl
654
+ }
655
+ }
656
+ }
657
+ }
658
+ }
659
+ """
660
+ )
661
+ assert self._client is not None
662
+ response = self._client.execute(
663
+ query,
664
+ variable_values={
665
+ "entityName": self._entity,
666
+ "projectName": self._project,
667
+ "name": self._name,
668
+ },
669
+ )
670
+ attrs = response["project"]["artifact"]
671
+ self._load_manifest(attrs["currentManifest"]["file"]["directUrl"])
672
+ assert self._manifest is not None
673
+ return self._manifest
674
+
675
+ @property
676
+ def digest(self) -> str:
677
+ """The logical digest of the artifact.
678
+
679
+ The digest is the checksum of the artifact's contents. If an artifact has the
680
+ same digest as the current `latest` version, then `log_artifact` is a no-op.
681
+ """
682
+ return self.manifest.digest()
683
+
684
+ @property
685
+ def size(self) -> int:
686
+ """The total size of the artifact in bytes.
687
+
688
+ Includes any references tracked by this artifact.
689
+ """
690
+ total_size: int = 0
691
+ for entry in self.manifest.entries.values():
692
+ if entry.size is not None:
693
+ total_size += entry.size
694
+ return total_size
695
+
696
+ @property
697
+ @ensure_logged
698
+ def commit_hash(self) -> str:
699
+ """The hash returned when this artifact was committed."""
700
+ assert self._commit_hash is not None
701
+ return self._commit_hash
702
+
703
+ @property
704
+ @ensure_logged
705
+ def file_count(self) -> int:
706
+ """The number of files (including references)."""
707
+ assert self._file_count is not None
708
+ return self._file_count
709
+
710
+ @property
711
+ @ensure_logged
712
+ def created_at(self) -> str:
713
+ """Timestamp when the artifact was created."""
714
+ assert self._created_at is not None
715
+ return self._created_at
716
+
717
+ @property
718
+ @ensure_logged
719
+ def updated_at(self) -> str:
720
+ """The time when the artifact was last updated."""
721
+ assert self._created_at is not None
722
+ return self._updated_at or self._created_at
723
+
724
+ # State management.
725
+
726
+ def finalize(self) -> None:
727
+ """Finalize the artifact version.
728
+
729
+ You cannot modify an artifact version once it is finalized because the artifact
730
+ is logged as a specific artifact version. Create a new artifact version
731
+ to log more data to an artifact. An artifact is automatically finalized
732
+ when you log the artifact with `log_artifact`.
733
+ """
734
+ self._final = True
735
+
736
+ def is_draft(self) -> bool:
737
+ """Check if artifact is not saved.
738
+
739
+ Returns: Boolean. `False` if artifact is saved. `True` if artifact is not saved.
740
+ """
741
+ return self._state == ArtifactState.PENDING
742
+
743
+ def _is_draft_save_started(self) -> bool:
744
+ return self._save_future is not None
745
+
746
+ def save(
747
+ self,
748
+ project: str | None = None,
749
+ settings: wandb.Settings | None = None,
750
+ ) -> None:
751
+ """Persist any changes made to the artifact.
752
+
753
+ If currently in a run, that run will log this artifact. If not currently in a
754
+ run, a run of type "auto" is created to track this artifact.
755
+
756
+ Arguments:
757
+ project: A project to use for the artifact in the case that a run is not
758
+ already in context.
759
+ settings: A settings object to use when initializing an automatic run. Most
760
+ commonly used in testing harness.
761
+ """
762
+ if self._state != ArtifactState.PENDING:
763
+ return self._update()
764
+
765
+ if self._incremental:
766
+ with telemetry.context() as tel:
767
+ tel.feature.artifact_incremental = True
768
+
769
+ if wandb.run is None:
770
+ if settings is None:
771
+ settings = wandb.Settings(silent="true")
772
+ with wandb.init( # type: ignore
773
+ entity=self._source_entity,
774
+ project=project or self._source_project,
775
+ job_type="auto",
776
+ settings=settings,
777
+ ) as run:
778
+ # redoing this here because in this branch we know we didn't
779
+ # have the run at the beginning of the method
780
+ if self._incremental:
781
+ with telemetry.context(run=run) as tel:
782
+ tel.feature.artifact_incremental = True
783
+ run.log_artifact(self)
784
+ else:
785
+ wandb.run.log_artifact(self)
786
+
787
+ def _set_save_future(
788
+ self, save_future: MessageFuture, client: RetryingClient
789
+ ) -> None:
790
+ self._save_future = save_future
791
+ self._client = client
792
+
793
+ def wait(self, timeout: int | None = None) -> Artifact:
794
+ """If needed, wait for this artifact to finish logging.
795
+
796
+ Arguments:
797
+ timeout: The time, in seconds, to wait.
798
+
799
+ Returns:
800
+ An `Artifact` object.
801
+ """
802
+ if self.is_draft():
803
+ if self._save_future is None:
804
+ raise ArtifactNotLoggedError(type(self).wait.__qualname__, self)
805
+ result = self._save_future.get(timeout)
806
+ if not result:
807
+ raise WaitTimeoutError(
808
+ "Artifact upload wait timed out, failed to fetch Artifact response"
809
+ )
810
+ response = result.response.log_artifact_response
811
+ if response.error_message:
812
+ raise ValueError(response.error_message)
813
+ self._populate_after_save(response.artifact_id)
814
+ return self
815
+
816
+ def _populate_after_save(self, artifact_id: str) -> None:
817
+ fields = InternalApi().server_artifact_introspection()
818
+
819
+ supports_ttl = "ttlIsInherited" in fields
820
+ ttl_duration_seconds = "ttlDurationSeconds" if supports_ttl else ""
821
+ ttl_is_inherited = "ttlIsInherited" if supports_ttl else ""
822
+
823
+ supports_tags = "tags" in fields
824
+ tags = "tags {name}" if supports_tags else ""
825
+
826
+ query_template = f"""
827
+ query ArtifactByIDShort($id: ID!) {{
828
+ artifact(id: $id) {{
829
+ artifactSequence {{
830
+ project {{
831
+ entityName
832
+ name
833
+ }}
834
+ name
835
+ }}
836
+ versionIndex
837
+ {ttl_duration_seconds}
838
+ {ttl_is_inherited}
839
+ aliases {{
840
+ artifactCollection {{
841
+ project {{
842
+ entityName
843
+ name
844
+ }}
845
+ name
846
+ }}
847
+ alias
848
+ }}
849
+ {tags!s}
850
+ state
851
+ currentManifest {{
852
+ file {{
853
+ directUrl
854
+ }}
855
+ }}
856
+ commitHash
857
+ fileCount
858
+ createdAt
859
+ updatedAt
860
+ }}
861
+ }}
862
+ """
863
+
864
+ query = gql(query_template)
865
+
866
+ assert self._client is not None
867
+ response = self._client.execute(
868
+ query,
869
+ variable_values={"id": artifact_id},
870
+ )
871
+ attrs = response.get("artifact")
872
+ if attrs is None:
873
+ raise ValueError(f"Unable to fetch artifact with id {artifact_id}")
874
+ self._id = artifact_id
875
+ attr_project = attrs["artifactSequence"]["project"]
876
+ self._entity = ""
877
+ self._project = ""
878
+ if attr_project:
879
+ self._entity = attr_project["entityName"]
880
+ self._project = attr_project["name"]
881
+ self._name = "{}:v{}".format(
882
+ attrs["artifactSequence"]["name"], attrs["versionIndex"]
883
+ )
884
+ self._version = "v{}".format(attrs["versionIndex"])
885
+ self._source_entity = self._entity
886
+ self._source_project = self._project
887
+ self._source_name = self._name
888
+ self._source_version = self._version
889
+ self._ttl_duration_seconds = self._ttl_duration_seconds_from_gql(
890
+ attrs.get("ttlDurationSeconds")
891
+ )
892
+ self._ttl_is_inherited = (
893
+ True if attrs.get("ttlIsInherited") is None else attrs["ttlIsInherited"]
894
+ )
895
+ self._ttl_changed = False # Reset after saving artifact
896
+ self._aliases = [
897
+ alias["alias"]
898
+ for alias in attrs["aliases"]
899
+ if alias["artifactCollection"]
900
+ and alias["artifactCollection"]["project"]
901
+ and alias["artifactCollection"]["project"]["entityName"] == self._entity
902
+ and alias["artifactCollection"]["project"]["name"] == self._project
903
+ and alias["artifactCollection"]["name"] == self._name.split(":")[0]
904
+ and not util.alias_is_version_index(alias["alias"])
905
+ ]
906
+ self._tags = [tag_obj["name"] for tag_obj in attrs.get("tags", [])]
907
+ self._state = ArtifactState(attrs["state"])
908
+ self._load_manifest(attrs["currentManifest"]["file"]["directUrl"])
909
+ self._commit_hash = attrs["commitHash"]
910
+ self._file_count = attrs["fileCount"]
911
+ self._created_at = attrs["createdAt"]
912
+ self._updated_at = attrs["updatedAt"]
913
+
914
+ @normalize_exceptions
915
+ def _update(self) -> None:
916
+ """Persists artifact changes to the wandb backend."""
917
+ aliases = None
918
+ introspect_query = gql(
919
+ """
920
+ query ProbeServerAddAliasesInput {
921
+ AddAliasesInputInfoType: __type(name: "AddAliasesInput") {
922
+ name
923
+ inputFields {
924
+ name
925
+ }
926
+ }
927
+ }
928
+ """
929
+ )
930
+ assert self._client is not None
931
+ response = self._client.execute(introspect_query)
932
+ if response.get("AddAliasesInputInfoType"): # wandb backend version >= 0.13.0
933
+ aliases_to_add = set(self._aliases) - set(self._saved_aliases)
934
+ aliases_to_delete = set(self._saved_aliases) - set(self._aliases)
935
+ if aliases_to_add:
936
+ add_mutation = gql(
937
+ """
938
+ mutation addAliases(
939
+ $artifactID: ID!,
940
+ $aliases: [ArtifactCollectionAliasInput!]!,
941
+ ) {
942
+ addAliases(
943
+ input: {artifactID: $artifactID, aliases: $aliases}
944
+ ) {
945
+ success
946
+ }
947
+ }
948
+ """
949
+ )
950
+ assert self._client is not None
951
+ self._client.execute(
952
+ add_mutation,
953
+ variable_values={
954
+ "artifactID": self.id,
955
+ "aliases": [
956
+ {
957
+ "entityName": self._entity,
958
+ "projectName": self._project,
959
+ "artifactCollectionName": self._name.split(":")[0],
960
+ "alias": alias,
961
+ }
962
+ for alias in aliases_to_add
963
+ ],
964
+ },
965
+ )
966
+ if aliases_to_delete:
967
+ delete_mutation = gql(
968
+ """
969
+ mutation deleteAliases(
970
+ $artifactID: ID!,
971
+ $aliases: [ArtifactCollectionAliasInput!]!,
972
+ ) {
973
+ deleteAliases(
974
+ input: {artifactID: $artifactID, aliases: $aliases}
975
+ ) {
976
+ success
977
+ }
978
+ }
979
+ """
980
+ )
981
+ assert self._client is not None
982
+ self._client.execute(
983
+ delete_mutation,
984
+ variable_values={
985
+ "artifactID": self.id,
986
+ "aliases": [
987
+ {
988
+ "entityName": self._entity,
989
+ "projectName": self._project,
990
+ "artifactCollectionName": self._name.split(":")[0],
991
+ "alias": alias,
992
+ }
993
+ for alias in aliases_to_delete
994
+ ],
995
+ },
996
+ )
997
+ self._saved_aliases = copy(self._aliases)
998
+ else: # wandb backend version < 0.13.0
999
+ aliases = [
1000
+ {
1001
+ "artifactCollectionName": self._name.split(":")[0],
1002
+ "alias": alias,
1003
+ }
1004
+ for alias in self._aliases
1005
+ ]
1006
+
1007
+ mutation_template = """
1008
+ mutation updateArtifact(
1009
+ $artifactID: ID!
1010
+ $description: String
1011
+ $metadata: JSONString
1012
+ _TTL_DURATION_SECONDS_TYPE_
1013
+ _TAGS_TO_ADD_TYPE_
1014
+ _TAGS_TO_DELETE_TYPE_
1015
+ $aliases: [ArtifactAliasInput!]
1016
+ ) {
1017
+ updateArtifact(
1018
+ input: {
1019
+ artifactID: $artifactID,
1020
+ description: $description,
1021
+ metadata: $metadata,
1022
+ _TTL_DURATION_SECONDS_VALUE_
1023
+ _TAGS_TO_ADD_VALUE_
1024
+ _TAGS_TO_DELETE_VALUE_
1025
+ aliases: $aliases
1026
+ }
1027
+ ) {
1028
+ artifact {
1029
+ id
1030
+ _TTL_DURATION_SECONDS_FIELDS_
1031
+ }
1032
+ }
1033
+ }
1034
+ """
1035
+ fields = InternalApi().server_artifact_introspection()
1036
+ if "ttlIsInherited" in fields:
1037
+ mutation_template = (
1038
+ mutation_template.replace(
1039
+ "_TTL_DURATION_SECONDS_TYPE_",
1040
+ "$ttlDurationSeconds: Int64",
1041
+ )
1042
+ .replace(
1043
+ "_TTL_DURATION_SECONDS_VALUE_",
1044
+ "ttlDurationSeconds: $ttlDurationSeconds",
1045
+ )
1046
+ .replace(
1047
+ "_TTL_DURATION_SECONDS_FIELDS_",
1048
+ "ttlDurationSeconds ttlIsInherited",
1049
+ )
1050
+ )
1051
+ else:
1052
+ if self._ttl_changed:
1053
+ termwarn(
1054
+ "Server not compatible with setting Artifact TTLs, please upgrade the server to use Artifact TTL"
1055
+ )
1056
+ mutation_template = (
1057
+ mutation_template.replace("_TTL_DURATION_SECONDS_TYPE_", "")
1058
+ .replace("_TTL_DURATION_SECONDS_VALUE_", "")
1059
+ .replace("_TTL_DURATION_SECONDS_FIELDS_", "")
1060
+ )
1061
+
1062
+ tags_to_add = validate_tags(set(self._tags) - set(self._saved_tags))
1063
+ tags_to_delete = validate_tags(set(self._saved_tags) - set(self._tags))
1064
+ if "tags" in fields:
1065
+ mutation_template = (
1066
+ mutation_template.replace(
1067
+ "_TAGS_TO_ADD_TYPE_", "$tagsToAdd: [TagInput!]"
1068
+ )
1069
+ .replace("_TAGS_TO_DELETE_TYPE_", "$tagsToDelete: [TagInput!]")
1070
+ .replace("_TAGS_TO_ADD_VALUE_", "tagsToAdd: $tagsToAdd")
1071
+ .replace("_TAGS_TO_DELETE_VALUE_", "tagsToDelete: $tagsToDelete")
1072
+ )
1073
+ else:
1074
+ if tags_to_add or tags_to_delete:
1075
+ termwarn(
1076
+ "Server not compatible with Artifact tags. "
1077
+ "To use Artifact tags, please upgrade the server to v0.85 or higher."
1078
+ )
1079
+ mutation_template = (
1080
+ mutation_template.replace("_TAGS_TO_ADD_TYPE_", "")
1081
+ .replace("_TAGS_TO_DELETE_TYPE_", "")
1082
+ .replace("_TAGS_TO_ADD_VALUE_", "")
1083
+ .replace("_TAGS_TO_DELETE_VALUE_", "")
1084
+ )
1085
+
1086
+ mutation = gql(mutation_template)
1087
+ assert self._client is not None
1088
+
1089
+ ttl_duration_input = self._ttl_duration_seconds_to_gql()
1090
+ response = self._client.execute(
1091
+ mutation,
1092
+ variable_values={
1093
+ "artifactID": self.id,
1094
+ "description": self.description,
1095
+ "metadata": util.json_dumps_safer(self.metadata),
1096
+ "ttlDurationSeconds": ttl_duration_input,
1097
+ "aliases": aliases,
1098
+ "tagsToAdd": [{"tagName": tag_name} for tag_name in tags_to_add],
1099
+ "tagsToDelete": [{"tagName": tag_name} for tag_name in tags_to_delete],
1100
+ },
1101
+ )
1102
+ attrs = response["updateArtifact"]["artifact"]
1103
+
1104
+ # Update ttl_duration_seconds based on updateArtifact
1105
+ self._ttl_duration_seconds = self._ttl_duration_seconds_from_gql(
1106
+ attrs.get("ttlDurationSeconds")
1107
+ )
1108
+ self._ttl_is_inherited = (
1109
+ True if attrs.get("ttlIsInherited") is None else attrs["ttlIsInherited"]
1110
+ )
1111
+ self._ttl_changed = False # Reset after updating artifact
1112
+
1113
+ # Adding, removing, getting entries.
1114
+
1115
+ def __getitem__(self, name: str) -> WBValue | None:
1116
+ """Get the WBValue object located at the artifact relative `name`.
1117
+
1118
+ Arguments:
1119
+ name: The artifact relative name to get.
1120
+
1121
+ Returns:
1122
+ W&B object that can be logged with `wandb.log()` and visualized in the W&B UI.
1123
+
1124
+ Raises:
1125
+ ArtifactNotLoggedError: If the artifact isn't logged or the run is offline.
1126
+ """
1127
+ return self.get(name)
1128
+
1129
+ def __setitem__(self, name: str, item: WBValue) -> ArtifactManifestEntry:
1130
+ """Add `item` to the artifact at path `name`.
1131
+
1132
+ Arguments:
1133
+ name: The path within the artifact to add the object.
1134
+ item: The object to add.
1135
+
1136
+ Returns:
1137
+ The added manifest entry
1138
+
1139
+ Raises:
1140
+ ArtifactFinalizedError: You cannot make changes to the current artifact
1141
+ version because it is finalized. Log a new artifact version instead.
1142
+ """
1143
+ return self.add(item, name)
1144
+
1145
+ @contextlib.contextmanager
1146
+ @ensure_not_finalized
1147
+ def new_file(
1148
+ self, name: str, mode: str = "w", encoding: str | None = None
1149
+ ) -> Iterator[IO]:
1150
+ """Open a new temporary file and add it to the artifact.
1151
+
1152
+ Arguments:
1153
+ name: The name of the new file to add to the artifact.
1154
+ mode: The file access mode to use to open the new file.
1155
+ encoding: The encoding used to open the new file.
1156
+
1157
+ Returns:
1158
+ A new file object that can be written to. Upon closing, the file will be
1159
+ automatically added to the artifact.
1160
+
1161
+ Raises:
1162
+ ArtifactFinalizedError: You cannot make changes to the current artifact
1163
+ version because it is finalized. Log a new artifact version instead.
1164
+ """
1165
+ if self._tmp_dir is None:
1166
+ self._tmp_dir = tempfile.TemporaryDirectory()
1167
+ path = os.path.join(self._tmp_dir.name, name.lstrip("/"))
1168
+ if os.path.exists(path):
1169
+ raise ValueError(f"File with name {name!r} already exists at {path!r}")
1170
+
1171
+ filesystem.mkdir_exists_ok(os.path.dirname(path))
1172
+ try:
1173
+ with util.fsync_open(path, mode, encoding) as f:
1174
+ yield f
1175
+ except UnicodeEncodeError as e:
1176
+ termerror(
1177
+ f"Failed to open the provided file (UnicodeEncodeError: {e}). Please "
1178
+ f"provide the proper encoding."
1179
+ )
1180
+ raise e
1181
+
1182
+ self.add_file(path, name=name, policy="immutable", skip_cache=True)
1183
+
1184
+ @ensure_not_finalized
1185
+ def add_file(
1186
+ self,
1187
+ local_path: str,
1188
+ name: str | None = None,
1189
+ is_tmp: bool | None = False,
1190
+ skip_cache: bool | None = False,
1191
+ policy: Literal["mutable", "immutable"] | None = "mutable",
1192
+ ) -> ArtifactManifestEntry:
1193
+ """Add a local file to the artifact.
1194
+
1195
+ Arguments:
1196
+ local_path: The path to the file being added.
1197
+ name: The path within the artifact to use for the file being added. Defaults
1198
+ to the basename of the file.
1199
+ is_tmp: If true, then the file is renamed deterministically to avoid
1200
+ collisions.
1201
+ skip_cache: If set to `True`, W&B will not copy files to the cache after uploading.
1202
+ policy: "mutable" | "immutable". By default, "mutable"
1203
+ "mutable": Create a temporary copy of the file to prevent corruption during upload.
1204
+ "immutable": Disable protection, rely on the user not to delete or change the file.
1205
+
1206
+ Returns:
1207
+ The added manifest entry
1208
+
1209
+ Raises:
1210
+ ArtifactFinalizedError: You cannot make changes to the current artifact
1211
+ version because it is finalized. Log a new artifact version instead.
1212
+ ValueError: Policy must be "mutable" or "immutable"
1213
+ """
1214
+ if not os.path.isfile(local_path):
1215
+ raise ValueError("Path is not a file: {}".format(local_path))
1216
+
1217
+ name = LogicalPath(name or os.path.basename(local_path))
1218
+ digest = md5_file_b64(local_path)
1219
+
1220
+ if is_tmp:
1221
+ file_path, file_name = os.path.split(name)
1222
+ file_name_parts = file_name.split(".")
1223
+ file_name_parts[0] = b64_to_hex_id(digest)[:20]
1224
+ name = os.path.join(file_path, ".".join(file_name_parts))
1225
+
1226
+ return self._add_local_file(
1227
+ name, local_path, digest=digest, skip_cache=skip_cache, policy=policy
1228
+ )
1229
+
1230
+ @ensure_not_finalized
1231
+ def add_dir(
1232
+ self,
1233
+ local_path: str,
1234
+ name: str | None = None,
1235
+ skip_cache: bool | None = False,
1236
+ policy: Literal["mutable", "immutable"] | None = "mutable",
1237
+ ) -> None:
1238
+ """Add a local directory to the artifact.
1239
+
1240
+ Arguments:
1241
+ local_path: The path of the local directory.
1242
+ name: The subdirectory name within an artifact. The name you specify appears
1243
+ in the W&B App UI nested by artifact's `type`.
1244
+ Defaults to the root of the artifact.
1245
+ skip_cache: If set to `True`, W&B will not copy/move files to the cache while uploading
1246
+ policy: "mutable" | "immutable". By default, "mutable"
1247
+ "mutable": Create a temporary copy of the file to prevent corruption during upload.
1248
+ "immutable": Disable protection, rely on the user not to delete or change the file.
1249
+
1250
+ Raises:
1251
+ ArtifactFinalizedError: You cannot make changes to the current artifact
1252
+ version because it is finalized. Log a new artifact version instead.
1253
+ ValueError: Policy must be "mutable" or "immutable"
1254
+ """
1255
+ if not os.path.isdir(local_path):
1256
+ raise ValueError("Path is not a directory: {}".format(local_path))
1257
+
1258
+ termlog(
1259
+ "Adding directory to artifact ({})... ".format(
1260
+ os.path.join(".", os.path.normpath(local_path))
1261
+ ),
1262
+ newline=False,
1263
+ )
1264
+ start_time = time.time()
1265
+
1266
+ paths = []
1267
+ for dirpath, _, filenames in os.walk(local_path, followlinks=True):
1268
+ for fname in filenames:
1269
+ physical_path = os.path.join(dirpath, fname)
1270
+ logical_path = os.path.relpath(physical_path, start=local_path)
1271
+ if name is not None:
1272
+ logical_path = os.path.join(name, logical_path)
1273
+ paths.append((logical_path, physical_path))
1274
+
1275
+ def add_manifest_file(log_phy_path: tuple[str, str]) -> None:
1276
+ logical_path, physical_path = log_phy_path
1277
+ self._add_local_file(
1278
+ name=logical_path,
1279
+ path=physical_path,
1280
+ skip_cache=skip_cache,
1281
+ policy=policy,
1282
+ )
1283
+
1284
+ num_threads = 8
1285
+ pool = multiprocessing.dummy.Pool(num_threads)
1286
+ pool.map(add_manifest_file, paths)
1287
+ pool.close()
1288
+ pool.join()
1289
+
1290
+ termlog("Done. %.1fs" % (time.time() - start_time), prefix=False)
1291
+
1292
+ @ensure_not_finalized
1293
+ def add_reference(
1294
+ self,
1295
+ uri: ArtifactManifestEntry | str,
1296
+ name: StrPath | None = None,
1297
+ checksum: bool = True,
1298
+ max_objects: int | None = None,
1299
+ ) -> Sequence[ArtifactManifestEntry]:
1300
+ """Add a reference denoted by a URI to the artifact.
1301
+
1302
+ Unlike files or directories that you add to an artifact, references are not
1303
+ uploaded to W&B. For more information,
1304
+ see [Track external files](https://docs.wandb.ai/guides/artifacts/track-external-files).
1305
+
1306
+ By default, the following schemes are supported:
1307
+
1308
+ - http(s): The size and digest of the file will be inferred by the
1309
+ `Content-Length` and the `ETag` response headers returned by the server.
1310
+ - s3: The checksum and size are pulled from the object metadata. If bucket
1311
+ versioning is enabled, then the version ID is also tracked.
1312
+ - gs: The checksum and size are pulled from the object metadata. If bucket
1313
+ versioning is enabled, then the version ID is also tracked.
1314
+ - https, domain matching `*.blob.core.windows.net` (Azure): The checksum and size
1315
+ are be pulled from the blob metadata. If storage account versioning is
1316
+ enabled, then the version ID is also tracked.
1317
+ - file: The checksum and size are pulled from the file system. This scheme
1318
+ is useful if you have an NFS share or other externally mounted volume
1319
+ containing files you wish to track but not necessarily upload.
1320
+
1321
+ For any other scheme, the digest is just a hash of the URI and the size is left
1322
+ blank.
1323
+
1324
+ Arguments:
1325
+ uri: The URI path of the reference to add. The URI path can be an object
1326
+ returned from `Artifact.get_entry` to store a reference to another
1327
+ artifact's entry.
1328
+ name: The path within the artifact to place the contents of this reference.
1329
+ checksum: Whether or not to checksum the resource(s) located at the
1330
+ reference URI. Checksumming is strongly recommended as it enables
1331
+ automatic integrity validation. Disabling checksumming will speed up
1332
+ artifact creation but reference directories will not iterated through so the
1333
+ objects in the directory will not be saved to the artifact. We recommend
1334
+ setting `checksum=False` when adding reference objects, in which case
1335
+ a new version will only be created if the reference URI changes.
1336
+ max_objects: The maximum number of objects to consider when adding a
1337
+ reference that points to directory or bucket store prefix. By default,
1338
+ the maximum number of objects allowed for Amazon S3,
1339
+ GCS, Azure, and local files is 10,000,000. Other URI schemas do not have a maximum.
1340
+
1341
+ Returns:
1342
+ The added manifest entries.
1343
+
1344
+ Raises:
1345
+ ArtifactFinalizedError: You cannot make changes to the current artifact
1346
+ version because it is finalized. Log a new artifact version instead.
1347
+ """
1348
+ if name is not None:
1349
+ name = LogicalPath(name)
1350
+
1351
+ # This is a bit of a hack, we want to check if the uri is a of the type
1352
+ # ArtifactManifestEntry. If so, then recover the reference URL.
1353
+ if isinstance(uri, ArtifactManifestEntry):
1354
+ uri_str = uri.ref_url()
1355
+ elif isinstance(uri, str):
1356
+ uri_str = uri
1357
+ url = urlparse(str(uri_str))
1358
+ if not url.scheme:
1359
+ raise ValueError(
1360
+ "References must be URIs. To reference a local file, use file://"
1361
+ )
1362
+
1363
+ manifest_entries = self._storage_policy.store_reference(
1364
+ self,
1365
+ URIStr(uri_str),
1366
+ name=name,
1367
+ checksum=checksum,
1368
+ max_objects=max_objects,
1369
+ )
1370
+ for entry in manifest_entries:
1371
+ self.manifest.add_entry(entry)
1372
+
1373
+ return manifest_entries
1374
+
1375
+ @ensure_not_finalized
1376
+ def add(self, obj: WBValue, name: StrPath) -> ArtifactManifestEntry:
1377
+ """Add wandb.WBValue `obj` to the artifact.
1378
+
1379
+ Arguments:
1380
+ obj: The object to add. Currently support one of Bokeh, JoinedTable,
1381
+ PartitionedTable, Table, Classes, ImageMask, BoundingBoxes2D, Audio,
1382
+ Image, Video, Html, Object3D
1383
+ name: The path within the artifact to add the object.
1384
+
1385
+ Returns:
1386
+ The added manifest entry
1387
+
1388
+ Raises:
1389
+ ArtifactFinalizedError: You cannot make changes to the current artifact
1390
+ version because it is finalized. Log a new artifact version instead.
1391
+ """
1392
+ name = LogicalPath(name)
1393
+
1394
+ # This is a "hack" to automatically rename tables added to
1395
+ # the wandb /media/tables directory to their sha-based name.
1396
+ # TODO: figure out a more appropriate convention.
1397
+ is_tmp_name = name.startswith("media/tables")
1398
+
1399
+ # Validate that the object is one of the correct wandb.Media types
1400
+ # TODO: move this to checking subclass of wandb.Media once all are
1401
+ # generally supported
1402
+ allowed_types = [
1403
+ data_types.Bokeh,
1404
+ data_types.JoinedTable,
1405
+ data_types.PartitionedTable,
1406
+ data_types.Table,
1407
+ data_types.Classes,
1408
+ data_types.ImageMask,
1409
+ data_types.BoundingBoxes2D,
1410
+ data_types.Audio,
1411
+ data_types.Image,
1412
+ data_types.Video,
1413
+ data_types.Html,
1414
+ data_types.Object3D,
1415
+ data_types.Molecule,
1416
+ data_types._SavedModel,
1417
+ ]
1418
+
1419
+ if not any(isinstance(obj, t) for t in allowed_types):
1420
+ raise ValueError(
1421
+ "Found object of type {}, expected one of {}.".format(
1422
+ obj.__class__, allowed_types
1423
+ )
1424
+ )
1425
+
1426
+ obj_id = id(obj)
1427
+ if obj_id in self._added_objs:
1428
+ return self._added_objs[obj_id][1]
1429
+
1430
+ # If the object is coming from another artifact, save it as a reference
1431
+ ref_path = obj._get_artifact_entry_ref_url()
1432
+ if ref_path is not None:
1433
+ return self.add_reference(ref_path, type(obj).with_suffix(name))[0]
1434
+
1435
+ val = obj.to_json(self)
1436
+ name = obj.with_suffix(name)
1437
+ entry = self.manifest.get_entry_by_path(name)
1438
+ if entry is not None:
1439
+ return entry
1440
+
1441
+ def do_write(f: IO) -> None:
1442
+ import json
1443
+
1444
+ # TODO: Do we need to open with utf-8 codec?
1445
+ f.write(json.dumps(val, sort_keys=True))
1446
+
1447
+ if is_tmp_name:
1448
+ file_path = os.path.join(self._TMP_DIR.name, str(id(self)), name)
1449
+ folder_path, _ = os.path.split(file_path)
1450
+ if not os.path.exists(folder_path):
1451
+ os.makedirs(folder_path)
1452
+ with open(file_path, "w") as tmp_f:
1453
+ do_write(tmp_f)
1454
+ else:
1455
+ with self.new_file(name) as f:
1456
+ file_path = f.name
1457
+ do_write(f)
1458
+
1459
+ # Note, we add the file from our temp directory.
1460
+ # It will be added again later on finalize, but succeed since
1461
+ # the checksum should match
1462
+ entry = self.add_file(file_path, name, is_tmp_name)
1463
+ # We store a reference to the obj so that its id doesn't get reused.
1464
+ self._added_objs[obj_id] = (obj, entry)
1465
+ if obj._artifact_target is None:
1466
+ obj._set_artifact_target(self, entry.path)
1467
+
1468
+ if is_tmp_name:
1469
+ if os.path.exists(file_path):
1470
+ os.remove(file_path)
1471
+
1472
+ return entry
1473
+
1474
+ def _add_local_file(
1475
+ self,
1476
+ name: StrPath,
1477
+ path: StrPath,
1478
+ digest: B64MD5 | None = None,
1479
+ skip_cache: bool | None = False,
1480
+ policy: Literal["mutable", "immutable"] | None = "mutable",
1481
+ ) -> ArtifactManifestEntry:
1482
+ policy = policy or "mutable"
1483
+ if policy not in ["mutable", "immutable"]:
1484
+ raise ValueError(
1485
+ f"Invalid policy `{policy}`. Policy may only be `mutable` or `immutable`."
1486
+ )
1487
+ upload_path = path
1488
+ if policy == "mutable":
1489
+ with tempfile.NamedTemporaryFile(dir=get_staging_dir(), delete=False) as f:
1490
+ staging_path = f.name
1491
+ shutil.copyfile(path, staging_path)
1492
+ # Set as read-only to prevent changes to the file during upload process
1493
+ os.chmod(staging_path, stat.S_IRUSR)
1494
+ upload_path = staging_path
1495
+
1496
+ entry = ArtifactManifestEntry(
1497
+ path=name,
1498
+ digest=digest or md5_file_b64(upload_path),
1499
+ size=os.path.getsize(upload_path),
1500
+ local_path=upload_path,
1501
+ skip_cache=skip_cache,
1502
+ )
1503
+ self.manifest.add_entry(entry)
1504
+ self._added_local_paths[os.fspath(path)] = entry
1505
+ return entry
1506
+
1507
+ @ensure_not_finalized
1508
+ def remove(self, item: StrPath | ArtifactManifestEntry) -> None:
1509
+ """Remove an item from the artifact.
1510
+
1511
+ Arguments:
1512
+ item: The item to remove. Can be a specific manifest entry or the name of an
1513
+ artifact-relative path. If the item matches a directory all items in
1514
+ that directory will be removed.
1515
+
1516
+ Raises:
1517
+ ArtifactFinalizedError: You cannot make changes to the current artifact
1518
+ version because it is finalized. Log a new artifact version instead.
1519
+ FileNotFoundError: If the item isn't found in the artifact.
1520
+ """
1521
+ if isinstance(item, ArtifactManifestEntry):
1522
+ self.manifest.remove_entry(item)
1523
+ return
1524
+
1525
+ path = str(PurePosixPath(item))
1526
+ entry = self.manifest.get_entry_by_path(path)
1527
+ if entry:
1528
+ self.manifest.remove_entry(entry)
1529
+ return
1530
+
1531
+ entries = self.manifest.get_entries_in_directory(path)
1532
+ if not entries:
1533
+ raise FileNotFoundError(f"No such file or directory: {path}")
1534
+ for entry in entries:
1535
+ self.manifest.remove_entry(entry)
1536
+
1537
+ def get_path(self, name: StrPath) -> ArtifactManifestEntry:
1538
+ """Deprecated. Use `get_entry(name)`."""
1539
+ deprecate(
1540
+ field_name=Deprecated.artifact__get_path,
1541
+ warning_message="Artifact.get_path(name) is deprecated, use Artifact.get_entry(name) instead.",
1542
+ )
1543
+ return self.get_entry(name)
1544
+
1545
+ @ensure_logged
1546
+ def get_entry(self, name: StrPath) -> ArtifactManifestEntry:
1547
+ """Get the entry with the given name.
1548
+
1549
+ Arguments:
1550
+ name: The artifact relative name to get
1551
+
1552
+ Returns:
1553
+ A `W&B` object.
1554
+
1555
+ Raises:
1556
+ ArtifactNotLoggedError: if the artifact isn't logged or the run is offline.
1557
+ KeyError: if the artifact doesn't contain an entry with the given name.
1558
+ """
1559
+ name = LogicalPath(name)
1560
+ entry = self.manifest.entries.get(name) or self._get_obj_entry(name)[0]
1561
+ if entry is None:
1562
+ raise KeyError("Path not contained in artifact: {}".format(name))
1563
+ entry._parent_artifact = self
1564
+ return entry
1565
+
1566
+ @ensure_logged
1567
+ def get(self, name: str) -> WBValue | None:
1568
+ """Get the WBValue object located at the artifact relative `name`.
1569
+
1570
+ Arguments:
1571
+ name: The artifact relative name to retrieve.
1572
+
1573
+ Returns:
1574
+ W&B object that can be logged with `wandb.log()` and visualized in the W&B UI.
1575
+
1576
+ Raises:
1577
+ ArtifactNotLoggedError: if the artifact isn't logged or the run is offline
1578
+ """
1579
+ entry, wb_class = self._get_obj_entry(name)
1580
+ if entry is None or wb_class is None:
1581
+ return None
1582
+
1583
+ # If the entry is a reference from another artifact, then get it directly from
1584
+ # that artifact.
1585
+ referenced_id = entry._referenced_artifact_id()
1586
+ if referenced_id:
1587
+ assert self._client is not None
1588
+ artifact = self._from_id(referenced_id, client=self._client)
1589
+ assert artifact is not None
1590
+ return artifact.get(util.uri_from_path(entry.ref))
1591
+
1592
+ # Special case for wandb.Table. This is intended to be a short term
1593
+ # optimization. Since tables are likely to download many other assets in
1594
+ # artifact(s), we eagerly download the artifact using the parallelized
1595
+ # `artifact.download`. In the future, we should refactor the deserialization
1596
+ # pattern such that this special case is not needed.
1597
+ if wb_class == wandb.Table:
1598
+ self.download()
1599
+
1600
+ # Get the ArtifactManifestEntry
1601
+ item = self.get_entry(entry.path)
1602
+ item_path = item.download()
1603
+
1604
+ # Load the object from the JSON blob
1605
+ result = None
1606
+ json_obj = {}
1607
+ with open(item_path) as file:
1608
+ json_obj = json.load(file)
1609
+ result = wb_class.from_json(json_obj, self)
1610
+ result._set_artifact_source(self, name)
1611
+ return result
1612
+
1613
+ def get_added_local_path_name(self, local_path: str) -> str | None:
1614
+ """Get the artifact relative name of a file added by a local filesystem path.
1615
+
1616
+ Arguments:
1617
+ local_path: The local path to resolve into an artifact relative name.
1618
+
1619
+ Returns:
1620
+ The artifact relative name.
1621
+ """
1622
+ entry = self._added_local_paths.get(local_path, None)
1623
+ if entry is None:
1624
+ return None
1625
+ return entry.path
1626
+
1627
+ def _get_obj_entry(
1628
+ self, name: str
1629
+ ) -> tuple[ArtifactManifestEntry, Type[WBValue]] | tuple[None, None]: # noqa: UP006 # `type` shadows `Artifact.type`
1630
+ """Return an object entry by name, handling any type suffixes.
1631
+
1632
+ When objects are added with `.add(obj, name)`, the name is typically changed to
1633
+ include the suffix of the object type when serializing to JSON. So we need to be
1634
+ able to resolve a name, without tasking the user with appending .THING.json.
1635
+ This method returns an entry if it exists by a suffixed name.
1636
+
1637
+ Arguments:
1638
+ name: name used when adding
1639
+ """
1640
+ for wb_class in WBValue.type_mapping().values():
1641
+ wandb_file_name = wb_class.with_suffix(name)
1642
+ entry = self.manifest.entries.get(wandb_file_name)
1643
+ if entry is not None:
1644
+ return entry, wb_class
1645
+ return None, None
1646
+
1647
+ # Downloading.
1648
+
1649
+ @ensure_logged
1650
+ def download(
1651
+ self,
1652
+ root: StrPath | None = None,
1653
+ allow_missing_references: bool = False,
1654
+ skip_cache: bool | None = None,
1655
+ path_prefix: StrPath | None = None,
1656
+ ) -> FilePathStr:
1657
+ """Download the contents of the artifact to the specified root directory.
1658
+
1659
+ Existing files located within `root` are not modified. Explicitly delete `root`
1660
+ before you call `download` if you want the contents of `root` to exactly match
1661
+ the artifact.
1662
+
1663
+ Arguments:
1664
+ root: The directory W&B stores the artifact's files.
1665
+ allow_missing_references: If set to `True`, any invalid reference paths
1666
+ will be ignored while downloading referenced files.
1667
+ skip_cache: If set to `True`, the artifact cache will be skipped when
1668
+ downloading and W&B will download each file into the default root or
1669
+ specified download directory.
1670
+ path_prefix: If specified, only files with a path that starts with the given
1671
+ prefix will be downloaded. Uses unix format (forward slashes).
1672
+
1673
+ Returns:
1674
+ The path to the downloaded contents.
1675
+
1676
+ Raises:
1677
+ ArtifactNotLoggedError: If the artifact is not logged.
1678
+ RuntimeError: If the artifact is attempted to be downloaded in offline mode.
1679
+ """
1680
+ root = FilePathStr(str(root or self._default_root()))
1681
+ self._add_download_root(root)
1682
+
1683
+ # TODO: we need a better way to check for offline mode across the app, as this is an anti-pattern
1684
+ if env.is_offline() or util._is_offline():
1685
+ raise RuntimeError("Cannot download artifacts in offline mode.")
1686
+
1687
+ # TODO: download artifacts using core when implemented
1688
+ # if is_require_core():
1689
+ # return self._download_using_core(
1690
+ # root=root,
1691
+ # allow_missing_references=allow_missing_references,
1692
+ # skip_cache=bool(skip_cache),
1693
+ # path_prefix=path_prefix,
1694
+ # )
1695
+ return self._download(
1696
+ root=root,
1697
+ allow_missing_references=allow_missing_references,
1698
+ skip_cache=skip_cache,
1699
+ path_prefix=path_prefix,
1700
+ )
1701
+
1702
+ def _download_using_core(
1703
+ self,
1704
+ root: str,
1705
+ allow_missing_references: bool = False,
1706
+ skip_cache: bool = False,
1707
+ path_prefix: StrPath | None = None,
1708
+ ) -> FilePathStr:
1709
+ import pathlib
1710
+
1711
+ from wandb.sdk.backend.backend import Backend
1712
+
1713
+ if wandb.run is None:
1714
+ # ensure wandb-core is up and running
1715
+ from wandb.sdk import wandb_setup
1716
+
1717
+ wl = wandb_setup.setup()
1718
+ assert wl is not None
1719
+
1720
+ stream_id = generate_id()
1721
+
1722
+ settings = wl.settings.to_proto()
1723
+ # TODO: remove this
1724
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
1725
+
1726
+ settings.sync_dir.value = str(tmp_dir)
1727
+ settings.sync_file.value = str(tmp_dir / f"{stream_id}.wandb")
1728
+ settings.files_dir.value = str(tmp_dir / "files")
1729
+ settings.run_id.value = stream_id
1730
+
1731
+ service = wl.service
1732
+ assert service
1733
+
1734
+ service.inform_init(settings=settings, run_id=stream_id)
1735
+
1736
+ mailbox = Mailbox()
1737
+ backend = Backend(
1738
+ settings=wl.settings,
1739
+ service=service,
1740
+ mailbox=mailbox,
1741
+ )
1742
+ backend.ensure_launched()
1743
+
1744
+ assert backend.interface
1745
+ backend.interface._stream_id = stream_id # type: ignore
1746
+
1747
+ mailbox.enable_keepalive()
1748
+ else:
1749
+ assert wandb.run._backend
1750
+ backend = wandb.run._backend
1751
+
1752
+ assert backend.interface
1753
+ handle = backend.interface.deliver_download_artifact(
1754
+ self.id, # type: ignore
1755
+ root,
1756
+ allow_missing_references,
1757
+ skip_cache,
1758
+ path_prefix, # type: ignore
1759
+ )
1760
+ # TODO: Start the download process in the user process too, to handle reference downloads
1761
+ self._download(
1762
+ root=root,
1763
+ allow_missing_references=allow_missing_references,
1764
+ skip_cache=skip_cache,
1765
+ path_prefix=path_prefix,
1766
+ )
1767
+ result = handle.wait(timeout=-1)
1768
+
1769
+ if result is None:
1770
+ handle.abandon()
1771
+ assert result is not None
1772
+ response = result.response.download_artifact_response
1773
+ if response.error_message:
1774
+ raise ValueError(f"Error downloading artifact: {response.error_message}")
1775
+
1776
+ return FilePathStr(root)
1777
+
1778
+ def _download(
1779
+ self,
1780
+ root: str,
1781
+ allow_missing_references: bool = False,
1782
+ skip_cache: bool | None = None,
1783
+ path_prefix: StrPath | None = None,
1784
+ ) -> FilePathStr:
1785
+ nfiles = len(self.manifest.entries)
1786
+ size = sum(e.size or 0 for e in self.manifest.entries.values())
1787
+ log = False
1788
+ if nfiles > 5000 or size > 50 * 1024 * 1024:
1789
+ log = True
1790
+ termlog(
1791
+ "Downloading large artifact {}, {:.2f}MB. {} files... ".format(
1792
+ self.name, size / (1024 * 1024), nfiles
1793
+ ),
1794
+ )
1795
+ start_time = datetime.now()
1796
+ download_logger = ArtifactDownloadLogger(nfiles=nfiles)
1797
+
1798
+ def _download_entry(
1799
+ entry: ArtifactManifestEntry,
1800
+ api_key: str | None,
1801
+ cookies: dict | None,
1802
+ headers: dict | None,
1803
+ ) -> None:
1804
+ _thread_local_api_settings.api_key = api_key
1805
+ _thread_local_api_settings.cookies = cookies
1806
+ _thread_local_api_settings.headers = headers
1807
+
1808
+ try:
1809
+ entry.download(root, skip_cache=skip_cache)
1810
+ except FileNotFoundError as e:
1811
+ if allow_missing_references:
1812
+ wandb.termwarn(str(e))
1813
+ return
1814
+ raise
1815
+ except _GCSIsADirectoryError as e:
1816
+ logger.debug(str(e))
1817
+ return
1818
+ download_logger.notify_downloaded()
1819
+
1820
+ download_entry = partial(
1821
+ _download_entry,
1822
+ api_key=_thread_local_api_settings.api_key,
1823
+ cookies=_thread_local_api_settings.cookies,
1824
+ headers=_thread_local_api_settings.headers,
1825
+ )
1826
+
1827
+ with concurrent.futures.ThreadPoolExecutor(64) as executor:
1828
+ active_futures = set()
1829
+ has_next_page = True
1830
+ cursor = None
1831
+ while has_next_page:
1832
+ fetch_url_batch_size = env.get_artifact_fetch_file_url_batch_size()
1833
+ attrs = self._fetch_file_urls(cursor, fetch_url_batch_size)
1834
+ has_next_page = attrs["pageInfo"]["hasNextPage"]
1835
+ cursor = attrs["pageInfo"]["endCursor"]
1836
+ for edge in attrs["edges"]:
1837
+ entry = self.get_entry(edge["node"]["name"])
1838
+ # TODO: uncomment once artifact downloads are supported in core
1839
+ # if require_core and entry.ref is None:
1840
+ # # Handled by core
1841
+ # continue
1842
+ entry._download_url = edge["node"]["directUrl"]
1843
+ if (not path_prefix) or entry.path.startswith(str(path_prefix)):
1844
+ active_futures.add(executor.submit(download_entry, entry))
1845
+ # Wait for download threads to catch up.
1846
+ max_backlog = fetch_url_batch_size
1847
+ if len(active_futures) > max_backlog:
1848
+ for future in concurrent.futures.as_completed(active_futures):
1849
+ future.result() # check for errors
1850
+ active_futures.remove(future)
1851
+ if len(active_futures) <= max_backlog:
1852
+ break
1853
+ # Check for errors.
1854
+ for future in concurrent.futures.as_completed(active_futures):
1855
+ future.result()
1856
+
1857
+ if log:
1858
+ now = datetime.now()
1859
+ delta = abs((now - start_time).total_seconds())
1860
+ hours = int(delta // 3600)
1861
+ minutes = int((delta - hours * 3600) // 60)
1862
+ seconds = delta - hours * 3600 - minutes * 60
1863
+ termlog(
1864
+ f"Done. {hours}:{minutes}:{seconds:.1f}",
1865
+ prefix=False,
1866
+ )
1867
+ return FilePathStr(root)
1868
+
1869
+ @retry.retriable(
1870
+ retry_timedelta=timedelta(minutes=3),
1871
+ retryable_exceptions=(requests.RequestException),
1872
+ )
1873
+ def _fetch_file_urls(self, cursor: str | None, per_page: int | None = 5000) -> Any:
1874
+ query = gql(
1875
+ """
1876
+ query ArtifactFileURLs($id: ID!, $cursor: String, $perPage: Int) {
1877
+ artifact(id: $id) {
1878
+ files(after: $cursor, first: $perPage) {
1879
+ pageInfo {
1880
+ hasNextPage
1881
+ endCursor
1882
+ }
1883
+ edges {
1884
+ node {
1885
+ name
1886
+ directUrl
1887
+ }
1888
+ }
1889
+ }
1890
+ }
1891
+ }
1892
+ """
1893
+ )
1894
+ assert self._client is not None
1895
+ response = self._client.execute(
1896
+ query,
1897
+ variable_values={"id": self.id, "cursor": cursor, "perPage": per_page},
1898
+ timeout=60,
1899
+ )
1900
+ return response["artifact"]["files"]
1901
+
1902
+ @ensure_logged
1903
+ def checkout(self, root: str | None = None) -> str:
1904
+ """Replace the specified root directory with the contents of the artifact.
1905
+
1906
+ WARNING: This will delete all files in `root` that are not included in the
1907
+ artifact.
1908
+
1909
+ Arguments:
1910
+ root: The directory to replace with this artifact's files.
1911
+
1912
+ Returns:
1913
+ The path of the checked out contents.
1914
+
1915
+ Raises:
1916
+ ArtifactNotLoggedError: If the artifact is not logged.
1917
+ """
1918
+ root = root or self._default_root(include_version=False)
1919
+
1920
+ for dirpath, _, files in os.walk(root):
1921
+ for file in files:
1922
+ full_path = os.path.join(dirpath, file)
1923
+ artifact_path = os.path.relpath(full_path, start=root)
1924
+ try:
1925
+ self.get_entry(artifact_path)
1926
+ except KeyError:
1927
+ # File is not part of the artifact, remove it.
1928
+ os.remove(full_path)
1929
+
1930
+ return self.download(root=root)
1931
+
1932
+ @ensure_logged
1933
+ def verify(self, root: str | None = None) -> None:
1934
+ """Verify that the contents of an artifact match the manifest.
1935
+
1936
+ All files in the directory are checksummed and the checksums are then
1937
+ cross-referenced against the artifact's manifest. References are not verified.
1938
+
1939
+ Arguments:
1940
+ root: The directory to verify. If None artifact will be downloaded to
1941
+ './artifacts/self.name/'
1942
+
1943
+ Raises:
1944
+ ArtifactNotLoggedError: If the artifact is not logged.
1945
+ ValueError: If the verification fails.
1946
+ """
1947
+ root = root or self._default_root()
1948
+
1949
+ for dirpath, _, files in os.walk(root):
1950
+ for file in files:
1951
+ full_path = os.path.join(dirpath, file)
1952
+ artifact_path = os.path.relpath(full_path, start=root)
1953
+ try:
1954
+ self.get_entry(artifact_path)
1955
+ except KeyError:
1956
+ raise ValueError(
1957
+ "Found file {} which is not a member of artifact {}".format(
1958
+ full_path, self.name
1959
+ )
1960
+ )
1961
+
1962
+ ref_count = 0
1963
+ for entry in self.manifest.entries.values():
1964
+ if entry.ref is None:
1965
+ if md5_file_b64(os.path.join(root, entry.path)) != entry.digest:
1966
+ raise ValueError("Digest mismatch for file: {}".format(entry.path))
1967
+ else:
1968
+ ref_count += 1
1969
+ if ref_count > 0:
1970
+ print("Warning: skipped verification of {} refs".format(ref_count))
1971
+
1972
+ @ensure_logged
1973
+ def file(self, root: str | None = None) -> StrPath:
1974
+ """Download a single file artifact to the directory you specify with `root`.
1975
+
1976
+ Arguments:
1977
+ root: The root directory to store the file. Defaults to
1978
+ './artifacts/self.name/'.
1979
+
1980
+ Returns:
1981
+ The full path of the downloaded file.
1982
+
1983
+ Raises:
1984
+ ArtifactNotLoggedError: If the artifact is not logged.
1985
+ ValueError: If the artifact contains more than one file.
1986
+ """
1987
+ if root is None:
1988
+ root = os.path.join(".", "artifacts", self.name)
1989
+
1990
+ if len(self.manifest.entries) > 1:
1991
+ raise ValueError(
1992
+ "This artifact contains more than one file, call `.download()` to get "
1993
+ 'all files or call .get_entry("filename").download()'
1994
+ )
1995
+
1996
+ return self.get_entry(list(self.manifest.entries)[0]).download(root)
1997
+
1998
+ @ensure_logged
1999
+ def files(
2000
+ self, names: list[str] | None = None, per_page: int = 50
2001
+ ) -> ArtifactFiles:
2002
+ """Iterate over all files stored in this artifact.
2003
+
2004
+ Arguments:
2005
+ names: The filename paths relative to the root of the artifact you wish to
2006
+ list.
2007
+ per_page: The number of files to return per request.
2008
+
2009
+ Returns:
2010
+ An iterator containing `File` objects.
2011
+
2012
+ Raises:
2013
+ ArtifactNotLoggedError: If the artifact is not logged.
2014
+ """
2015
+ return ArtifactFiles(self._client, self, names, per_page)
2016
+
2017
+ def _default_root(self, include_version: bool = True) -> FilePathStr:
2018
+ name = self.source_name if include_version else self.source_name.split(":")[0]
2019
+ root = os.path.join(env.get_artifact_dir(), name)
2020
+ # In case we're on a system where the artifact dir has a name corresponding to
2021
+ # an unexpected filesystem, we'll check for alternate roots. If one exists we'll
2022
+ # use that, otherwise we'll fall back to the system-preferred path.
2023
+ path = filesystem.check_exists(root) or filesystem.system_preferred_path(root)
2024
+ return FilePathStr(str(path))
2025
+
2026
+ def _add_download_root(self, dir_path: str) -> None:
2027
+ self._download_roots.add(os.path.abspath(dir_path))
2028
+
2029
+ def _local_path_to_name(self, file_path: str) -> str | None:
2030
+ """Convert a local file path to a path entry in the artifact."""
2031
+ abs_file_path = os.path.abspath(file_path)
2032
+ abs_file_parts = abs_file_path.split(os.sep)
2033
+ for i in range(len(abs_file_parts) + 1):
2034
+ if os.path.join(os.sep, *abs_file_parts[:i]) in self._download_roots:
2035
+ return os.path.join(*abs_file_parts[i:])
2036
+ return None
2037
+
2038
+ # Others.
2039
+
2040
+ @ensure_logged
2041
+ def delete(self, delete_aliases: bool = False) -> None:
2042
+ """Delete an artifact and its files.
2043
+
2044
+ If called on a linked artifact (i.e. a member of a portfolio collection): only the link is deleted, and the
2045
+ source artifact is unaffected.
2046
+
2047
+ Arguments:
2048
+ delete_aliases: If set to `True`, deletes all aliases associated with the artifact.
2049
+ Otherwise, this raises an exception if the artifact has existing
2050
+ aliases.
2051
+ This parameter is ignored if the artifact is linked (i.e. a member of a portfolio collection).
2052
+
2053
+ Raises:
2054
+ ArtifactNotLoggedError: If the artifact is not logged.
2055
+ """
2056
+ if self.collection.is_sequence():
2057
+ self._delete(delete_aliases)
2058
+ else:
2059
+ self._unlink()
2060
+
2061
+ @normalize_exceptions
2062
+ def _delete(self, delete_aliases: bool = False) -> None:
2063
+ mutation = gql(
2064
+ """
2065
+ mutation DeleteArtifact($artifactID: ID!, $deleteAliases: Boolean) {
2066
+ deleteArtifact(input: {
2067
+ artifactID: $artifactID
2068
+ deleteAliases: $deleteAliases
2069
+ }) {
2070
+ artifact {
2071
+ id
2072
+ }
2073
+ }
2074
+ }
2075
+ """
2076
+ )
2077
+ assert self._client is not None
2078
+ self._client.execute(
2079
+ mutation,
2080
+ variable_values={
2081
+ "artifactID": self.id,
2082
+ "deleteAliases": delete_aliases,
2083
+ },
2084
+ )
2085
+
2086
+ @normalize_exceptions
2087
+ def link(self, target_path: str, aliases: list[str] | None = None) -> None:
2088
+ """Link this artifact to a portfolio (a promoted collection of artifacts).
2089
+
2090
+ Arguments:
2091
+ target_path: The path to the portfolio inside a project.
2092
+ The target path must adhere to one of the following
2093
+ schemas `{portfolio}`, `{project}/{portfolio}` or
2094
+ `{entity}/{project}/{portfolio}`.
2095
+ To link the artifact to the Model Registry, rather than to a generic
2096
+ portfolio inside a project, set `target_path` to the following
2097
+ schema `{"model-registry"}/{Registered Model Name}` or
2098
+ `{entity}/{"model-registry"}/{Registered Model Name}`.
2099
+ aliases: A list of strings that uniquely identifies the artifact inside the
2100
+ specified portfolio.
2101
+
2102
+ Raises:
2103
+ ArtifactNotLoggedError: If the artifact is not logged.
2104
+ """
2105
+ if wandb.run is None:
2106
+ with wandb.init( # type: ignore
2107
+ entity=self._source_entity,
2108
+ project=self._source_project,
2109
+ job_type="auto",
2110
+ settings=wandb.Settings(silent="true"),
2111
+ ) as run:
2112
+ run.link_artifact(self, target_path, aliases)
2113
+ else:
2114
+ wandb.run.link_artifact(self, target_path, aliases)
2115
+
2116
+ @ensure_logged
2117
+ def unlink(self) -> None:
2118
+ """Unlink this artifact if it is currently a member of a portfolio (a promoted collection of artifacts).
2119
+
2120
+ Raises:
2121
+ ArtifactNotLoggedError: If the artifact is not logged.
2122
+ ValueError: If the artifact is not linked, i.e. it is not a member of a portfolio collection.
2123
+ """
2124
+ # Fail early if this isn't a linked artifact to begin with
2125
+ if self.collection.is_sequence():
2126
+ raise ValueError(
2127
+ f"Artifact {self.qualified_name!r} is not a linked artifact and cannot be unlinked. "
2128
+ f"To delete it, use {self.delete.__qualname__!r} instead."
2129
+ )
2130
+
2131
+ self._unlink()
2132
+
2133
+ @normalize_exceptions
2134
+ def _unlink(self) -> None:
2135
+ mutation = gql(
2136
+ """
2137
+ mutation UnlinkArtifact($artifactID: ID!, $artifactPortfolioID: ID!) {
2138
+ unlinkArtifact(
2139
+ input: { artifactID: $artifactID, artifactPortfolioID: $artifactPortfolioID }
2140
+ ) {
2141
+ artifactID
2142
+ success
2143
+ clientMutationId
2144
+ }
2145
+ }
2146
+ """
2147
+ )
2148
+ assert self._client is not None
2149
+ self._client.execute(
2150
+ mutation,
2151
+ variable_values={
2152
+ "artifactID": self.id,
2153
+ "artifactPortfolioID": self.collection.id,
2154
+ },
2155
+ )
2156
+
2157
+ @ensure_logged
2158
+ def used_by(self) -> list[Run]:
2159
+ """Get a list of the runs that have used this artifact.
2160
+
2161
+ Returns:
2162
+ A list of `Run` objects.
2163
+
2164
+ Raises:
2165
+ ArtifactNotLoggedError: If the artifact is not logged.
2166
+ """
2167
+ query = gql(
2168
+ """
2169
+ query ArtifactUsedBy(
2170
+ $id: ID!,
2171
+ ) {
2172
+ artifact(id: $id) {
2173
+ usedBy {
2174
+ edges {
2175
+ node {
2176
+ name
2177
+ project {
2178
+ name
2179
+ entityName
2180
+ }
2181
+ }
2182
+ }
2183
+ }
2184
+ }
2185
+ }
2186
+ """
2187
+ )
2188
+ assert self._client is not None
2189
+ response = self._client.execute(
2190
+ query,
2191
+ variable_values={"id": self.id},
2192
+ )
2193
+ return [
2194
+ Run(
2195
+ self._client,
2196
+ edge["node"]["project"]["entityName"],
2197
+ edge["node"]["project"]["name"],
2198
+ edge["node"]["name"],
2199
+ )
2200
+ for edge in response.get("artifact", {}).get("usedBy", {}).get("edges", [])
2201
+ ]
2202
+
2203
+ @ensure_logged
2204
+ def logged_by(self) -> Run | None:
2205
+ """Get the W&B run that originally logged the artifact.
2206
+
2207
+ Returns:
2208
+ The name of the W&B run that originally logged the artifact.
2209
+
2210
+ Raises:
2211
+ ArtifactNotLoggedError: If the artifact is not logged.
2212
+ """
2213
+ query = gql(
2214
+ """
2215
+ query ArtifactCreatedBy(
2216
+ $id: ID!
2217
+ ) {
2218
+ artifact(id: $id) {
2219
+ createdBy {
2220
+ ... on Run {
2221
+ name
2222
+ project {
2223
+ name
2224
+ entityName
2225
+ }
2226
+ }
2227
+ }
2228
+ }
2229
+ }
2230
+ """
2231
+ )
2232
+ assert self._client is not None
2233
+ response = self._client.execute(
2234
+ query,
2235
+ variable_values={"id": self.id},
2236
+ )
2237
+ creator = response.get("artifact", {}).get("createdBy", {})
2238
+ if creator.get("name") is None:
2239
+ return None
2240
+ return Run(
2241
+ self._client,
2242
+ creator["project"]["entityName"],
2243
+ creator["project"]["name"],
2244
+ creator["name"],
2245
+ )
2246
+
2247
+ @ensure_logged
2248
+ def json_encode(self) -> dict[str, Any]:
2249
+ """Returns the artifact encoded to the JSON format.
2250
+
2251
+ Returns:
2252
+ A `dict` with `string` keys representing attributes of the artifact.
2253
+ """
2254
+ return util.artifact_to_json(self)
2255
+
2256
+ @staticmethod
2257
+ def _expected_type(
2258
+ entity_name: str, project_name: str, name: str, client: RetryingClient
2259
+ ) -> str | None:
2260
+ """Returns the expected type for a given artifact name and project."""
2261
+ query = gql(
2262
+ """
2263
+ query ArtifactType(
2264
+ $entityName: String,
2265
+ $projectName: String,
2266
+ $name: String!
2267
+ ) {
2268
+ project(name: $projectName, entityName: $entityName) {
2269
+ artifact(name: $name) {
2270
+ artifactType {
2271
+ name
2272
+ }
2273
+ }
2274
+ }
2275
+ }
2276
+ """
2277
+ )
2278
+ if ":" not in name:
2279
+ name += ":latest"
2280
+ response = client.execute(
2281
+ query,
2282
+ variable_values={
2283
+ "entityName": entity_name,
2284
+ "projectName": project_name,
2285
+ "name": name,
2286
+ },
2287
+ )
2288
+ return (
2289
+ ((response.get("project") or {}).get("artifact") or {}).get("artifactType")
2290
+ or {}
2291
+ ).get("name")
2292
+
2293
+ @staticmethod
2294
+ def _normalize_metadata(metadata: dict[str, Any] | None) -> dict[str, Any]:
2295
+ if metadata is None:
2296
+ return {}
2297
+ if not isinstance(metadata, dict):
2298
+ raise TypeError(f"metadata must be dict, not {type(metadata)}")
2299
+ return cast(
2300
+ Dict[str, Any], json.loads(json.dumps(util.json_friendly_val(metadata)))
2301
+ )
2302
+
2303
+ def _load_manifest(self, url: str) -> None:
2304
+ with requests.get(url) as request:
2305
+ request.raise_for_status()
2306
+ self._manifest = ArtifactManifest.from_manifest_json(
2307
+ json.loads(util.ensure_text(request.content))
2308
+ )
2309
+
2310
+ @staticmethod
2311
+ def _get_gql_artifact_fragment() -> str:
2312
+ fields = InternalApi().server_artifact_introspection()
2313
+ fragment = """
2314
+ fragment ArtifactFragment on Artifact {
2315
+ id
2316
+ artifactSequence {
2317
+ project {
2318
+ entityName
2319
+ name
2320
+ }
2321
+ name
2322
+ }
2323
+ versionIndex
2324
+ artifactType {
2325
+ name
2326
+ }
2327
+ description
2328
+ metadata
2329
+ ttlDurationSeconds
2330
+ ttlIsInherited
2331
+ aliases {
2332
+ artifactCollection {
2333
+ project {
2334
+ entityName
2335
+ name
2336
+ }
2337
+ name
2338
+ }
2339
+ alias
2340
+ }
2341
+ _MAYBE_TAGS_
2342
+ state
2343
+ commitHash
2344
+ fileCount
2345
+ createdAt
2346
+ updatedAt
2347
+ }
2348
+ """
2349
+ if "ttlIsInherited" not in fields:
2350
+ fragment = fragment.replace("ttlDurationSeconds", "").replace(
2351
+ "ttlIsInherited", ""
2352
+ )
2353
+
2354
+ if "tags" in fields:
2355
+ fragment = fragment.replace("_MAYBE_TAGS_", "tags {name}")
2356
+ else:
2357
+ fragment = fragment.replace("_MAYBE_TAGS_", "")
2358
+
2359
+ return fragment
2360
+
2361
+ def _ttl_duration_seconds_to_gql(self) -> int | None:
2362
+ # Set artifact ttl value to ttl_duration_seconds if the user set a value
2363
+ # otherwise use ttl_status to indicate the backend INHERIT(-1) or DISABLED(-2) when the TTL is None
2364
+ # When ttl_change = None its a no op since nothing changed
2365
+ INHERIT = -1 # noqa: N806
2366
+ DISABLED = -2 # noqa: N806
2367
+
2368
+ if not self._ttl_changed:
2369
+ return None
2370
+ if self._ttl_is_inherited:
2371
+ return INHERIT
2372
+ return self._ttl_duration_seconds or DISABLED
2373
+
2374
+ def _ttl_duration_seconds_from_gql(
2375
+ self, gql_ttl_duration_seconds: int | None
2376
+ ) -> int | None:
2377
+ # If gql_ttl_duration_seconds is not positive, its indicating that TTL is DISABLED(-2)
2378
+ # gql_ttl_duration_seconds only returns None if the server is not compatible with setting Artifact TTLs
2379
+ if gql_ttl_duration_seconds and gql_ttl_duration_seconds > 0:
2380
+ return gql_ttl_duration_seconds
2381
+ return None
2382
+
2383
+
2384
+ class _ArtifactVersionType(WBType):
2385
+ name = "artifactVersion"
2386
+ types = [Artifact]
2387
+
2388
+
2389
+ TypeRegistry.add(_ArtifactVersionType)