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