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