sagemaker-core 1.0.62__py3-none-any.whl → 2.3.1__py3-none-any.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.
- sagemaker/__init__.py +2 -0
- sagemaker/core/__init__.py +16 -0
- sagemaker/core/_studio.py +116 -0
- sagemaker/core/_version.py +11 -0
- sagemaker/core/accept_types.py +131 -0
- sagemaker/core/analytics.py +744 -0
- sagemaker/core/apiutils/__init__.py +13 -0
- sagemaker/core/apiutils/_base_types.py +228 -0
- sagemaker/core/apiutils/_boto_functions.py +130 -0
- sagemaker/core/apiutils/_utils.py +34 -0
- sagemaker/core/base_deserializers.py +35 -0
- sagemaker/core/base_serializers.py +35 -0
- sagemaker/core/clarify/__init__.py +2898 -0
- sagemaker/core/collection.py +467 -0
- sagemaker/core/common_utils.py +2399 -0
- sagemaker/core/compute_resource_requirements/__init__.py +18 -0
- sagemaker/core/compute_resource_requirements/resource_requirements.py +94 -0
- sagemaker/core/config/__init__.py +181 -0
- sagemaker/core/config/config.py +238 -0
- sagemaker/core/config/config_manager.py +595 -0
- sagemaker/core/config/config_schema.py +1220 -0
- sagemaker/core/config/config_utils.py +297 -0
- {sagemaker_core/main → sagemaker/core}/config_schema.py +408 -3
- sagemaker/core/constants.py +73 -0
- sagemaker/core/content_types.py +137 -0
- sagemaker/core/debugger/__init__.py +39 -0
- sagemaker/core/debugger/debugger.py +945 -0
- sagemaker/core/debugger/framework_profile.py +292 -0
- sagemaker/core/debugger/metrics_config.py +468 -0
- sagemaker/core/debugger/profiler.py +42 -0
- sagemaker/core/debugger/profiler_config.py +190 -0
- sagemaker/core/debugger/profiler_constants.py +40 -0
- sagemaker/core/debugger/utils.py +148 -0
- sagemaker/core/deprecations.py +254 -0
- sagemaker/core/deserializers/__init__.py +10 -0
- sagemaker/core/deserializers/base.py +424 -0
- sagemaker/core/deserializers/implementations.py +157 -0
- sagemaker/core/drift_check_baselines.py +106 -0
- sagemaker/core/enums.py +51 -0
- sagemaker/core/environment_variables.py +101 -0
- sagemaker/core/exceptions.py +108 -0
- sagemaker/core/experiments/__init__.py +53 -0
- sagemaker/core/experiments/_api_types.py +251 -0
- sagemaker/core/experiments/_environment.py +124 -0
- sagemaker/core/experiments/_helper.py +294 -0
- sagemaker/core/experiments/_metrics.py +333 -0
- sagemaker/core/experiments/_run_context.py +58 -0
- sagemaker/core/experiments/_utils.py +216 -0
- sagemaker/core/experiments/experiment.py +247 -0
- sagemaker/core/experiments/run.py +970 -0
- sagemaker/core/experiments/trial.py +296 -0
- sagemaker/core/experiments/trial_component.py +387 -0
- sagemaker/core/explainer/__init__.py +24 -0
- sagemaker/core/explainer/clarify_explainer_config.py +298 -0
- sagemaker/core/explainer/explainer_config.py +44 -0
- sagemaker/core/fw_utils.py +1220 -0
- sagemaker/core/git_utils.py +415 -0
- sagemaker/core/helper/pipeline_variable.py +82 -0
- sagemaker/core/helper/session_helper.py +2977 -0
- sagemaker/core/hyperparameters.py +172 -0
- sagemaker/core/image_retriever/__init__.py +3 -0
- sagemaker/core/image_retriever/image_retriever.py +640 -0
- sagemaker/core/image_retriever/image_retriever_utils.py +509 -0
- sagemaker/core/image_retriever/test.py +7 -0
- sagemaker/core/image_uri_config/autogluon.json +1335 -0
- sagemaker/core/image_uri_config/blazingtext.json +50 -0
- sagemaker/core/image_uri_config/chainer.json +104 -0
- sagemaker/core/image_uri_config/clarify.json +39 -0
- sagemaker/core/image_uri_config/coach-mxnet.json +70 -0
- sagemaker/core/image_uri_config/coach-tensorflow.json +186 -0
- sagemaker/core/image_uri_config/data-wrangler.json +91 -0
- sagemaker/core/image_uri_config/debugger.json +34 -0
- sagemaker/core/image_uri_config/detailed-profiler.json +18 -0
- sagemaker/core/image_uri_config/djl-deepspeed.json +385 -0
- sagemaker/core/image_uri_config/djl-fastertransformer.json +167 -0
- sagemaker/core/image_uri_config/djl-lmi.json +136 -0
- sagemaker/core/image_uri_config/djl-neuronx.json +258 -0
- sagemaker/core/image_uri_config/djl-tensorrtllm.json +262 -0
- sagemaker/core/image_uri_config/factorization-machines.json +50 -0
- sagemaker/core/image_uri_config/forecasting-deepar.json +50 -0
- sagemaker/core/image_uri_config/huggingface-llm-neuronx.json +770 -0
- sagemaker/core/image_uri_config/huggingface-llm.json +1267 -0
- sagemaker/core/image_uri_config/huggingface-neuron.json +52 -0
- sagemaker/core/image_uri_config/huggingface-neuronx.json +686 -0
- sagemaker/core/image_uri_config/huggingface-tei-cpu.json +298 -0
- sagemaker/core/image_uri_config/huggingface-tei.json +298 -0
- sagemaker/core/image_uri_config/huggingface-training-compiler.json +195 -0
- sagemaker/core/image_uri_config/huggingface-vllm-neuronx.json +38 -0
- sagemaker/core/image_uri_config/huggingface.json +2287 -0
- sagemaker/core/image_uri_config/hyperpod-recipes-neuron.json +52 -0
- sagemaker/core/image_uri_config/image-classification-neo.json +43 -0
- sagemaker/core/image_uri_config/image-classification.json +50 -0
- sagemaker/core/image_uri_config/inferentia-mxnet.json +88 -0
- sagemaker/core/image_uri_config/inferentia-pytorch.json +127 -0
- sagemaker/core/image_uri_config/inferentia-tensorflow.json +88 -0
- sagemaker/core/image_uri_config/instance_gpu_info.json +782 -0
- sagemaker/core/image_uri_config/ipinsights.json +50 -0
- sagemaker/core/image_uri_config/kmeans.json +50 -0
- sagemaker/core/image_uri_config/knn.json +50 -0
- sagemaker/core/image_uri_config/lda.json +26 -0
- sagemaker/core/image_uri_config/linear-learner.json +50 -0
- sagemaker/core/image_uri_config/model-monitor.json +42 -0
- sagemaker/core/image_uri_config/mxnet.json +1154 -0
- sagemaker/core/image_uri_config/neo-mxnet.json +64 -0
- sagemaker/core/image_uri_config/neo-pytorch.json +341 -0
- sagemaker/core/image_uri_config/neo-tensorflow.json +109 -0
- sagemaker/core/image_uri_config/ntm.json +50 -0
- sagemaker/core/image_uri_config/object-detection.json +50 -0
- sagemaker/core/image_uri_config/object2vec.json +50 -0
- sagemaker/core/image_uri_config/pca.json +50 -0
- sagemaker/core/image_uri_config/pytorch-neuron.json +43 -0
- sagemaker/core/image_uri_config/pytorch-smp.json +218 -0
- sagemaker/core/image_uri_config/pytorch-training-compiler.json +80 -0
- sagemaker/core/image_uri_config/pytorch.json +3101 -0
- sagemaker/core/image_uri_config/randomcutforest.json +50 -0
- sagemaker/core/image_uri_config/ray-pytorch.json +46 -0
- sagemaker/core/image_uri_config/ray-tensorflow.json +194 -0
- sagemaker/core/image_uri_config/sagemaker-base-python.json +46 -0
- sagemaker/core/image_uri_config/sagemaker-distribution.json +37 -0
- sagemaker/core/image_uri_config/sagemaker-geospatial.json +13 -0
- sagemaker/core/image_uri_config/sagemaker-tritonserver.json +252 -0
- sagemaker/core/image_uri_config/semantic-segmentation.json +50 -0
- sagemaker/core/image_uri_config/seq2seq.json +50 -0
- sagemaker/core/image_uri_config/sklearn.json +494 -0
- sagemaker/core/image_uri_config/spark.json +280 -0
- sagemaker/core/image_uri_config/sparkml-serving.json +97 -0
- sagemaker/core/image_uri_config/stabilityai.json +53 -0
- sagemaker/core/image_uri_config/tensorflow.json +5086 -0
- sagemaker/core/image_uri_config/vw.json +25 -0
- sagemaker/core/image_uri_config/xgboost-neo.json +43 -0
- sagemaker/core/image_uri_config/xgboost.json +972 -0
- sagemaker/core/image_uris.py +816 -0
- sagemaker/core/inference_config.py +144 -0
- sagemaker/core/inference_recommender/__init__.py +18 -0
- sagemaker/core/inference_recommender/inference_recommender_mixin.py +622 -0
- sagemaker/core/inputs.py +366 -0
- sagemaker/core/instance_group.py +61 -0
- sagemaker/core/instance_types.py +164 -0
- sagemaker/core/instance_types_gpu_info.py +43 -0
- sagemaker/core/interactive_apps/__init__.py +41 -0
- sagemaker/core/interactive_apps/base_interactive_app.py +204 -0
- sagemaker/core/interactive_apps/detail_profiler_app.py +139 -0
- sagemaker/core/interactive_apps/tensorboard.py +149 -0
- sagemaker/core/iterators.py +197 -0
- sagemaker/core/job.py +380 -0
- sagemaker/core/jumpstart/__init__.py +156 -0
- sagemaker/core/jumpstart/accessors.py +390 -0
- sagemaker/core/jumpstart/artifacts/__init__.py +69 -0
- sagemaker/core/jumpstart/artifacts/environment_variables.py +252 -0
- sagemaker/core/jumpstart/artifacts/hyperparameters.py +120 -0
- sagemaker/core/jumpstart/artifacts/image_uris.py +139 -0
- sagemaker/core/jumpstart/artifacts/incremental_training.py +87 -0
- sagemaker/core/jumpstart/artifacts/instance_types.py +223 -0
- sagemaker/core/jumpstart/artifacts/kwargs.py +289 -0
- sagemaker/core/jumpstart/artifacts/metric_definitions.py +117 -0
- sagemaker/core/jumpstart/artifacts/model_packages.py +202 -0
- sagemaker/core/jumpstart/artifacts/model_uris.py +252 -0
- sagemaker/core/jumpstart/artifacts/payloads.py +96 -0
- sagemaker/core/jumpstart/artifacts/predictors.py +540 -0
- sagemaker/core/jumpstart/artifacts/resource_names.py +86 -0
- sagemaker/core/jumpstart/artifacts/resource_requirements.py +162 -0
- sagemaker/core/jumpstart/artifacts/script_uris.py +172 -0
- sagemaker/core/jumpstart/cache.py +663 -0
- sagemaker/core/jumpstart/configs.py +50 -0
- sagemaker/core/jumpstart/constants.py +198 -0
- sagemaker/core/jumpstart/deserializers.py +81 -0
- sagemaker/core/jumpstart/document.py +76 -0
- sagemaker/core/jumpstart/enums.py +168 -0
- sagemaker/core/jumpstart/exceptions.py +236 -0
- sagemaker/core/jumpstart/factory/utils.py +833 -0
- sagemaker/core/jumpstart/filters.py +597 -0
- sagemaker/core/jumpstart/hub/constants.py +16 -0
- sagemaker/core/jumpstart/hub/hub.py +291 -0
- sagemaker/core/jumpstart/hub/interfaces.py +936 -0
- sagemaker/core/jumpstart/hub/parser_utils.py +70 -0
- sagemaker/core/jumpstart/hub/parsers.py +288 -0
- sagemaker/core/jumpstart/hub/types.py +35 -0
- sagemaker/core/jumpstart/hub/utils.py +260 -0
- sagemaker/core/jumpstart/models.py +501 -0
- sagemaker/core/jumpstart/notebook_utils.py +575 -0
- sagemaker/core/jumpstart/parameters.py +20 -0
- sagemaker/core/jumpstart/payload_utils.py +239 -0
- sagemaker/core/jumpstart/region_config.json +171 -0
- sagemaker/core/jumpstart/search.py +171 -0
- sagemaker/core/jumpstart/serializers.py +81 -0
- sagemaker/core/jumpstart/session_utils.py +234 -0
- sagemaker/core/jumpstart/types.py +3044 -0
- sagemaker/core/jumpstart/utils.py +1731 -0
- sagemaker/core/jumpstart/validators.py +257 -0
- sagemaker/core/lambda_helper.py +312 -0
- sagemaker/core/lineage/__init__.py +42 -0
- sagemaker/core/lineage/_api_types.py +239 -0
- sagemaker/core/lineage/_utils.py +49 -0
- sagemaker/core/lineage/action.py +345 -0
- sagemaker/core/lineage/artifact.py +646 -0
- sagemaker/core/lineage/association.py +190 -0
- sagemaker/core/lineage/context.py +505 -0
- sagemaker/core/lineage/lineage_trial_component.py +191 -0
- sagemaker/core/lineage/query.py +732 -0
- sagemaker/core/lineage/visualizer.py +346 -0
- sagemaker/core/local/__init__.py +18 -0
- sagemaker/core/local/data.py +423 -0
- sagemaker/core/local/entities.py +678 -0
- sagemaker/core/local/exceptions.py +17 -0
- sagemaker/core/local/image.py +1243 -0
- sagemaker/core/local/local_session.py +739 -0
- sagemaker/core/local/utils.py +246 -0
- sagemaker/core/logs.py +181 -0
- sagemaker/core/metadata_properties.py +56 -0
- sagemaker/core/metric_definitions.py +91 -0
- sagemaker/core/mlflow/__init__.py +38 -0
- sagemaker/core/mlflow/forward_sagemaker_metrics.py +44 -0
- sagemaker/core/model_card/__init__.py +26 -0
- sagemaker/core/model_life_cycle.py +51 -0
- sagemaker/core/model_metrics.py +160 -0
- sagemaker/core/model_monitor/__init__.py +66 -0
- sagemaker/core/model_monitor/clarify_model_monitoring.py +1497 -0
- sagemaker/core/model_monitor/cron_expression_generator.py +82 -0
- sagemaker/core/model_monitor/data_capture_config.py +115 -0
- sagemaker/core/model_monitor/data_quality_monitoring_config.py +66 -0
- sagemaker/core/model_monitor/dataset_format.py +102 -0
- sagemaker/core/model_monitor/model_monitoring.py +4266 -0
- sagemaker/core/model_monitor/monitoring_alert.py +76 -0
- sagemaker/core/model_monitor/monitoring_files.py +506 -0
- sagemaker/core/model_monitor/utils.py +793 -0
- sagemaker/core/model_registry.py +480 -0
- sagemaker/core/model_uris.py +97 -0
- sagemaker/core/modules/__init__.py +19 -0
- sagemaker/core/modules/configs.py +239 -0
- sagemaker/core/modules/constants.py +37 -0
- sagemaker/core/modules/distributed.py +182 -0
- sagemaker/core/modules/local_core/local_container.py +605 -0
- sagemaker/core/modules/templates.py +83 -0
- sagemaker/core/modules/train/__init__.py +14 -0
- sagemaker/core/modules/train/container_drivers/__init__.py +14 -0
- sagemaker/core/modules/train/container_drivers/common/__init__.py +14 -0
- sagemaker/core/modules/train/container_drivers/common/utils.py +205 -0
- sagemaker/core/modules/train/container_drivers/distributed_drivers/__init__.py +14 -0
- sagemaker/core/modules/train/container_drivers/distributed_drivers/basic_script_driver.py +81 -0
- sagemaker/core/modules/train/container_drivers/distributed_drivers/mpi_driver.py +123 -0
- sagemaker/core/modules/train/container_drivers/distributed_drivers/mpi_utils.py +302 -0
- sagemaker/core/modules/train/container_drivers/distributed_drivers/torchrun_driver.py +129 -0
- sagemaker/core/modules/train/container_drivers/scripts/__init__.py +14 -0
- sagemaker/core/modules/train/container_drivers/scripts/environment.py +305 -0
- sagemaker/core/modules/train/sm_recipes/__init__.py +0 -0
- sagemaker/core/modules/train/sm_recipes/utils.py +330 -0
- sagemaker/core/modules/types.py +19 -0
- sagemaker/core/modules/utils.py +194 -0
- sagemaker/core/network.py +185 -0
- sagemaker/core/parameter.py +173 -0
- sagemaker/core/payloads.py +185 -0
- sagemaker/core/processing.py +1599 -0
- sagemaker/core/remote_function/__init__.py +19 -0
- sagemaker/core/remote_function/checkpoint_location.py +47 -0
- sagemaker/core/remote_function/client.py +1310 -0
- sagemaker/core/remote_function/core/__init__.py +0 -0
- sagemaker/core/remote_function/core/_custom_dispatch_table.py +72 -0
- sagemaker/core/remote_function/core/pipeline_variables.py +347 -0
- sagemaker/core/remote_function/core/serialization.py +410 -0
- sagemaker/core/remote_function/core/stored_function.py +223 -0
- sagemaker/core/remote_function/custom_file_filter.py +128 -0
- sagemaker/core/remote_function/errors.py +102 -0
- sagemaker/core/remote_function/invoke_function.py +167 -0
- sagemaker/core/remote_function/job.py +2121 -0
- sagemaker/core/remote_function/logging_config.py +38 -0
- sagemaker/core/remote_function/runtime_environment/__init__.py +14 -0
- sagemaker/core/remote_function/runtime_environment/bootstrap_runtime_environment.py +605 -0
- sagemaker/core/remote_function/runtime_environment/mpi_utils_remote.py +252 -0
- sagemaker/core/remote_function/runtime_environment/runtime_environment_manager.py +554 -0
- sagemaker/core/remote_function/runtime_environment/spark_app.py +18 -0
- sagemaker/core/remote_function/spark_config.py +149 -0
- sagemaker/core/resource_requirements.py +168 -0
- {sagemaker_core/main → sagemaker/core}/resources.py +19098 -10895
- sagemaker/core/s3/__init__.py +41 -0
- sagemaker/core/s3/client.py +367 -0
- sagemaker/core/s3/utils.py +175 -0
- sagemaker/core/script_uris.py +93 -0
- sagemaker/core/serializers/__init__.py +11 -0
- sagemaker/core/serializers/base.py +510 -0
- sagemaker/core/serializers/implementations.py +159 -0
- sagemaker/core/serializers/utils.py +223 -0
- sagemaker/core/serverless_inference_config.py +63 -0
- sagemaker/core/session_settings.py +55 -0
- sagemaker/core/shapes/__init__.py +3 -0
- sagemaker/core/shapes/model_card_shapes.py +159 -0
- {sagemaker_core/main → sagemaker/core/shapes}/shapes.py +5810 -1806
- sagemaker/core/spark/__init__.py +16 -0
- sagemaker/core/spark/defaults.py +16 -0
- sagemaker/core/spark/processing.py +1380 -0
- sagemaker/core/telemetry/__init__.py +23 -0
- sagemaker/core/telemetry/constants.py +82 -0
- sagemaker/core/telemetry/telemetry_logging.py +285 -0
- sagemaker/core/tools/__init__.py +1 -0
- {sagemaker_core → sagemaker/core}/tools/codegen.py +4 -4
- {sagemaker_core → sagemaker/core}/tools/constants.py +23 -15
- {sagemaker_core → sagemaker/core}/tools/data_extractor.py +1 -1
- {sagemaker_core → sagemaker/core}/tools/method.py +1 -1
- sagemaker/core/tools/model_card/generate_model_card_from_schema.py +562 -0
- {sagemaker_core → sagemaker/core}/tools/resources_codegen.py +165 -98
- {sagemaker_core → sagemaker/core}/tools/resources_extractor.py +5 -13
- {sagemaker_core → sagemaker/core}/tools/shapes_codegen.py +16 -17
- {sagemaker_core → sagemaker/core}/tools/shapes_extractor.py +29 -67
- {sagemaker_core → sagemaker/core}/tools/templates.py +39 -17
- sagemaker/core/training/__init__.py +14 -0
- sagemaker/core/training/configs.py +345 -0
- sagemaker/core/training/constants.py +37 -0
- sagemaker/core/training/utils.py +77 -0
- sagemaker/core/training_compiler/__init__.py +16 -0
- sagemaker/core/training_compiler/config.py +197 -0
- sagemaker/core/training_compiler_config.py +197 -0
- sagemaker/core/transformer.py +793 -0
- sagemaker/core/user_agent.py +76 -0
- sagemaker/core/utilities/__init__.py +24 -0
- sagemaker/core/utilities/cache.py +169 -0
- sagemaker/core/utilities/search_expression.py +133 -0
- sagemaker/core/utils/__init__.py +48 -0
- sagemaker/core/utils/code_injection/__init__.py +0 -0
- {sagemaker_core/main → sagemaker/core/utils}/code_injection/codec.py +2 -2
- {sagemaker_core/main → sagemaker/core/utils}/code_injection/shape_dag.py +5979 -176
- {sagemaker_core/main → sagemaker/core/utils}/exceptions.py +8 -8
- sagemaker_core/main/default_configs_helper.py → sagemaker/core/utils/intelligent_defaults_helper.py +5 -6
- {sagemaker_core/main → sagemaker/core/utils}/logs.py +1 -2
- {sagemaker_core/main → sagemaker/core/utils}/utils.py +27 -22
- sagemaker/core/workflow/__init__.py +152 -0
- sagemaker/core/workflow/conditions.py +313 -0
- sagemaker/core/workflow/entities.py +58 -0
- sagemaker/core/workflow/execution_variables.py +89 -0
- sagemaker/core/workflow/functions.py +193 -0
- sagemaker/core/workflow/parameters.py +222 -0
- sagemaker/core/workflow/pipeline_context.py +394 -0
- sagemaker/core/workflow/pipeline_definition_config.py +31 -0
- sagemaker/core/workflow/properties.py +285 -0
- sagemaker/core/workflow/step_outputs.py +65 -0
- sagemaker/core/workflow/utilities.py +514 -0
- sagemaker/lineage/__init__.py +33 -0
- sagemaker/lineage/action.py +28 -0
- sagemaker/lineage/artifact.py +28 -0
- sagemaker/lineage/context.py +28 -0
- sagemaker/lineage/lineage_trial_component.py +28 -0
- {sagemaker_core-1.0.62.dist-info → sagemaker_core-2.3.1.dist-info}/METADATA +28 -9
- sagemaker_core-2.3.1.dist-info/RECORD +351 -0
- sagemaker_core-2.3.1.dist-info/top_level.txt +1 -0
- sagemaker_core/_version.py +0 -3
- sagemaker_core/helper/session_helper.py +0 -769
- sagemaker_core/resources/__init__.py +0 -1
- sagemaker_core/shapes/__init__.py +0 -1
- sagemaker_core/tools/__init__.py +0 -1
- sagemaker_core-1.0.62.dist-info/RECORD +0 -35
- sagemaker_core-1.0.62.dist-info/top_level.txt +0 -1
- {sagemaker_core → sagemaker/core/helper}/__init__.py +0 -0
- {sagemaker_core/helper → sagemaker/core/jumpstart/factory}/__init__.py +0 -0
- {sagemaker_core/main → sagemaker/core/jumpstart/hub}/__init__.py +0 -0
- {sagemaker_core/main/code_injection → sagemaker/core/modules/local_core}/__init__.py +0 -0
- {sagemaker_core/main → sagemaker/core/utils}/code_injection/base.py +0 -0
- {sagemaker_core/main → sagemaker/core/utils}/code_injection/constants.py +0 -0
- {sagemaker_core/main → sagemaker/core/utils}/user_agent.py +0 -0
- {sagemaker_core-1.0.62.dist-info → sagemaker_core-2.3.1.dist-info}/WHEEL +0 -0
- {sagemaker_core-1.0.62.dist-info → sagemaker_core-2.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
"""SageMaker runtime environment module. This must be kept independent of SageMaker PySDK"""
|
|
14
|
+
|
|
15
|
+
from __future__ import absolute_import
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import sys
|
|
20
|
+
import shlex
|
|
21
|
+
import os
|
|
22
|
+
import subprocess
|
|
23
|
+
import time
|
|
24
|
+
import dataclasses
|
|
25
|
+
import json
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _UTCFormatter(logging.Formatter):
|
|
29
|
+
"""Class that overrides the default local time provider in log formatter."""
|
|
30
|
+
|
|
31
|
+
converter = time.gmtime
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_logger():
|
|
35
|
+
"""Return a logger with the name 'sagemaker'"""
|
|
36
|
+
sagemaker_logger = logging.getLogger("sagemaker.remote_function")
|
|
37
|
+
if len(sagemaker_logger.handlers) == 0:
|
|
38
|
+
sagemaker_logger.setLevel(logging.INFO)
|
|
39
|
+
handler = logging.StreamHandler()
|
|
40
|
+
formatter = _UTCFormatter("%(asctime)s %(name)s %(levelname)-8s %(message)s")
|
|
41
|
+
handler.setFormatter(formatter)
|
|
42
|
+
sagemaker_logger.addHandler(handler)
|
|
43
|
+
# don't stream logs with the root logger handler
|
|
44
|
+
sagemaker_logger.propagate = 0
|
|
45
|
+
|
|
46
|
+
return sagemaker_logger
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
logger = get_logger()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclasses.dataclass
|
|
53
|
+
class _DependencySettings:
|
|
54
|
+
"""Dependency settings for the remote function.
|
|
55
|
+
|
|
56
|
+
Instructs the runtime environment script on how to handle dependencies.
|
|
57
|
+
If ``dependency_file`` is set, the runtime environment script will attempt
|
|
58
|
+
to install the dependencies. If ``dependency_file`` is not set, the runtime
|
|
59
|
+
environment script will assume no dependencies are required.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
dependency_file: str = None
|
|
63
|
+
|
|
64
|
+
def to_string(self):
|
|
65
|
+
"""Converts the dependency settings to a string."""
|
|
66
|
+
return json.dumps(dataclasses.asdict(self))
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def from_string(dependency_settings_string):
|
|
70
|
+
"""Converts a json string to dependency settings.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
dependency_settings_string (str): The json string to convert.
|
|
74
|
+
"""
|
|
75
|
+
if dependency_settings_string is None:
|
|
76
|
+
return None
|
|
77
|
+
dependency_settings_dict = json.loads(dependency_settings_string)
|
|
78
|
+
return _DependencySettings(dependency_settings_dict.get("dependency_file"))
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def from_dependency_file_path(dependency_file_path):
|
|
82
|
+
"""Converts a dependency file path to dependency settings.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
dependency_file_path (str): The path to the dependency file.
|
|
86
|
+
"""
|
|
87
|
+
if dependency_file_path is None:
|
|
88
|
+
return _DependencySettings()
|
|
89
|
+
if dependency_file_path == "auto_capture":
|
|
90
|
+
return _DependencySettings("env_snapshot.yml")
|
|
91
|
+
return _DependencySettings(os.path.basename(dependency_file_path))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class RuntimeEnvironmentManager:
|
|
95
|
+
"""Runtime Environment Manager class to manage runtime environment."""
|
|
96
|
+
|
|
97
|
+
def _validate_path(self, path: str) -> str:
|
|
98
|
+
"""Validate and sanitize file path to prevent path traversal attacks.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
path (str): The file path to validate
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
str: The validated absolute path
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
ValueError: If the path is invalid or contains suspicious patterns
|
|
108
|
+
"""
|
|
109
|
+
if not path:
|
|
110
|
+
raise ValueError("Path cannot be empty")
|
|
111
|
+
|
|
112
|
+
# Get absolute path to prevent path traversal
|
|
113
|
+
abs_path = os.path.abspath(path)
|
|
114
|
+
|
|
115
|
+
# Check for null bytes (common in path traversal attacks)
|
|
116
|
+
if '\x00' in path:
|
|
117
|
+
raise ValueError(f"Invalid path contains null byte: {path}")
|
|
118
|
+
|
|
119
|
+
return abs_path
|
|
120
|
+
|
|
121
|
+
def _validate_env_name(self, env_name: str) -> None:
|
|
122
|
+
"""Validate conda environment name to prevent command injection.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
env_name (str): The environment name to validate
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
ValueError: If the environment name contains invalid characters
|
|
129
|
+
"""
|
|
130
|
+
if not env_name:
|
|
131
|
+
raise ValueError("Environment name cannot be empty")
|
|
132
|
+
|
|
133
|
+
# Allow only alphanumeric, underscore, and hyphen
|
|
134
|
+
import re
|
|
135
|
+
if not re.match(r'^[a-zA-Z0-9_-]+$', env_name):
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"Invalid environment name '{env_name}'. "
|
|
138
|
+
"Only alphanumeric characters, underscores, and hyphens are allowed."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def snapshot(self, dependencies: str = None) -> str:
|
|
142
|
+
"""Creates snapshot of the user's environment
|
|
143
|
+
|
|
144
|
+
If a req.txt or conda.yml file is provided, it verifies their existence and
|
|
145
|
+
returns the local file path
|
|
146
|
+
If ``auto_capture`` is set, this method will take the snapshot of
|
|
147
|
+
user's dependencies installed in the local runtime.
|
|
148
|
+
Current support for ``auto_capture``:
|
|
149
|
+
* conda env, generate a yml file and return it's local path
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
dependencies (str): Local path where dependencies file exists.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
file path of the existing or generated dependencies file
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
# No additional dependencies specified
|
|
159
|
+
if dependencies is None:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
if dependencies == "auto_capture":
|
|
163
|
+
return self._capture_from_local_runtime()
|
|
164
|
+
|
|
165
|
+
# Dependencies specified as either req.txt or conda_env.yml
|
|
166
|
+
if (
|
|
167
|
+
dependencies.endswith(".txt")
|
|
168
|
+
or dependencies.endswith(".yml")
|
|
169
|
+
or dependencies.endswith(".yaml")
|
|
170
|
+
):
|
|
171
|
+
self._is_file_exists(dependencies)
|
|
172
|
+
return dependencies
|
|
173
|
+
|
|
174
|
+
raise ValueError(f'Invalid dependencies provided: "{dependencies}"')
|
|
175
|
+
|
|
176
|
+
def _capture_from_local_runtime(self) -> str:
|
|
177
|
+
"""Generates dependencies list from the user's local runtime.
|
|
178
|
+
|
|
179
|
+
Raises RuntimeEnvironmentError if not able to.
|
|
180
|
+
|
|
181
|
+
Currently supports: conda environments
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
# Try to capture dependencies from the conda environment, if any.
|
|
185
|
+
conda_env_name = self._get_active_conda_env_name()
|
|
186
|
+
conda_env_prefix = self._get_active_conda_env_prefix()
|
|
187
|
+
if conda_env_name:
|
|
188
|
+
logger.info("Found conda_env_name: '%s'", conda_env_name)
|
|
189
|
+
elif conda_env_prefix:
|
|
190
|
+
logger.info("Found conda_env_prefix: '%s'", conda_env_prefix)
|
|
191
|
+
else:
|
|
192
|
+
raise ValueError("No conda environment seems to be active.")
|
|
193
|
+
|
|
194
|
+
if conda_env_name == "base":
|
|
195
|
+
logger.warning(
|
|
196
|
+
"We recommend using an environment other than base to "
|
|
197
|
+
"isolate your project dependencies from conda dependencies"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
local_dependencies_path = os.path.join(os.getcwd(), "env_snapshot.yml")
|
|
201
|
+
self._export_conda_env_from_prefix(conda_env_prefix, local_dependencies_path)
|
|
202
|
+
|
|
203
|
+
return local_dependencies_path
|
|
204
|
+
|
|
205
|
+
def _get_active_conda_env_prefix(self) -> str:
|
|
206
|
+
"""Returns the conda prefix from the set environment variable. None otherwise."""
|
|
207
|
+
return os.getenv("CONDA_PREFIX")
|
|
208
|
+
|
|
209
|
+
def _get_active_conda_env_name(self) -> str:
|
|
210
|
+
"""Returns the conda environment name from the set environment variable. None otherwise."""
|
|
211
|
+
return os.getenv("CONDA_DEFAULT_ENV")
|
|
212
|
+
|
|
213
|
+
def bootstrap(
|
|
214
|
+
self, local_dependencies_file: str, client_python_version: str, conda_env: str = None
|
|
215
|
+
):
|
|
216
|
+
"""Bootstraps the runtime environment by installing the additional dependencies if any.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
local_dependencies_file (str): path where dependencies file exists.
|
|
220
|
+
conda_env (str): conda environment to be activated. Default is None.
|
|
221
|
+
|
|
222
|
+
Returns: None
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
if local_dependencies_file.endswith(".txt"):
|
|
226
|
+
if conda_env:
|
|
227
|
+
self._install_req_txt_in_conda_env(conda_env, local_dependencies_file)
|
|
228
|
+
self._write_conda_env_to_file(conda_env)
|
|
229
|
+
|
|
230
|
+
else:
|
|
231
|
+
self._install_requirements_txt(local_dependencies_file, _python_executable())
|
|
232
|
+
|
|
233
|
+
elif local_dependencies_file.endswith(".yml") or local_dependencies_file.endswith(".yaml"):
|
|
234
|
+
if conda_env:
|
|
235
|
+
self._update_conda_env(conda_env, local_dependencies_file)
|
|
236
|
+
else:
|
|
237
|
+
conda_env = "sagemaker-runtime-env"
|
|
238
|
+
self._create_conda_env(conda_env, local_dependencies_file)
|
|
239
|
+
self._validate_python_version(client_python_version, conda_env)
|
|
240
|
+
self._write_conda_env_to_file(conda_env)
|
|
241
|
+
|
|
242
|
+
def run_pre_exec_script(self, pre_exec_script_path: str):
|
|
243
|
+
"""Runs script of pre-execution commands if existing.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
pre_exec_script_path (str): Path to pre-execution command script file.
|
|
247
|
+
"""
|
|
248
|
+
if os.path.isfile(pre_exec_script_path):
|
|
249
|
+
logger.info("Running pre-execution commands in '%s'", pre_exec_script_path)
|
|
250
|
+
return_code, error_logs = _run_pre_execution_command_script(pre_exec_script_path)
|
|
251
|
+
|
|
252
|
+
if return_code:
|
|
253
|
+
error_message = (
|
|
254
|
+
f"Encountered error while running pre-execution commands. Reason: {error_logs}"
|
|
255
|
+
)
|
|
256
|
+
raise RuntimeEnvironmentError(error_message)
|
|
257
|
+
else:
|
|
258
|
+
logger.info(
|
|
259
|
+
"'%s' does not exist. Assuming no pre-execution commands to run",
|
|
260
|
+
pre_exec_script_path,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def change_dir_permission(self, dirs: list, new_permission: str):
|
|
264
|
+
"""Change the permission of given directories
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
dirs (list[str]): A list of directories for permission update.
|
|
268
|
+
new_permission (str): The new permission for the given directories.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
_ERROR_MSG_PREFIX = "Failed to change directory permissions due to: "
|
|
272
|
+
command = ["sudo", "chmod", "-R", new_permission] + dirs
|
|
273
|
+
logger.info("Executing '%s'.", " ".join(command))
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
subprocess.run(command, check=True, stderr=subprocess.PIPE)
|
|
277
|
+
except subprocess.CalledProcessError as called_process_err:
|
|
278
|
+
err_msg = called_process_err.stderr.decode("utf-8")
|
|
279
|
+
raise RuntimeEnvironmentError(f"{_ERROR_MSG_PREFIX} {err_msg}")
|
|
280
|
+
except FileNotFoundError as file_not_found_err:
|
|
281
|
+
if "[Errno 2] No such file or directory: 'sudo'" in str(file_not_found_err):
|
|
282
|
+
raise RuntimeEnvironmentError(
|
|
283
|
+
f"{_ERROR_MSG_PREFIX} {file_not_found_err}. "
|
|
284
|
+
"Please contact the image owner to install 'sudo' in the job container "
|
|
285
|
+
"and provide sudo privilege to the container user."
|
|
286
|
+
)
|
|
287
|
+
raise RuntimeEnvironmentError(file_not_found_err)
|
|
288
|
+
|
|
289
|
+
def _is_file_exists(self, dependencies):
|
|
290
|
+
"""Check whether the dependencies file exists at the given location.
|
|
291
|
+
|
|
292
|
+
Raises error if not
|
|
293
|
+
"""
|
|
294
|
+
if not os.path.isfile(dependencies):
|
|
295
|
+
raise ValueError(f'No dependencies file named "{dependencies}" was found.')
|
|
296
|
+
|
|
297
|
+
def _install_requirements_txt(self, local_path, python_executable):
|
|
298
|
+
"""Install requirements.txt file"""
|
|
299
|
+
# Validate path to prevent command injection
|
|
300
|
+
validated_path = self._validate_path(local_path)
|
|
301
|
+
cmd = [python_executable, "-m", "pip", "install", "-r", validated_path, "-U"]
|
|
302
|
+
logger.info("Running command: '%s' in the dir: '%s' ", " ".join(cmd), os.getcwd())
|
|
303
|
+
_run_shell_cmd(cmd)
|
|
304
|
+
logger.info("Command %s ran successfully", " ".join(cmd))
|
|
305
|
+
|
|
306
|
+
def _create_conda_env(self, env_name, local_path):
|
|
307
|
+
"""Create conda env using conda yml file"""
|
|
308
|
+
# Validate inputs to prevent command injection
|
|
309
|
+
self._validate_env_name(env_name)
|
|
310
|
+
validated_path = self._validate_path(local_path)
|
|
311
|
+
|
|
312
|
+
cmd = [self._get_conda_exe(), "env", "create", "-n", env_name, "--file", validated_path]
|
|
313
|
+
logger.info("Creating conda environment %s using: %s.", env_name, " ".join(cmd))
|
|
314
|
+
_run_shell_cmd(cmd)
|
|
315
|
+
logger.info("Conda environment %s created successfully.", env_name)
|
|
316
|
+
|
|
317
|
+
def _install_req_txt_in_conda_env(self, env_name, local_path):
|
|
318
|
+
"""Install requirements.txt in the given conda environment"""
|
|
319
|
+
# Validate inputs to prevent command injection
|
|
320
|
+
self._validate_env_name(env_name)
|
|
321
|
+
validated_path = self._validate_path(local_path)
|
|
322
|
+
|
|
323
|
+
cmd = [self._get_conda_exe(), "run", "-n", env_name, "pip", "install", "-r", validated_path, "-U"]
|
|
324
|
+
logger.info("Activating conda env and installing requirements: %s", " ".join(cmd))
|
|
325
|
+
_run_shell_cmd(cmd)
|
|
326
|
+
logger.info("Requirements installed successfully in conda env %s", env_name)
|
|
327
|
+
|
|
328
|
+
def _update_conda_env(self, env_name, local_path):
|
|
329
|
+
"""Update conda env using conda yml file"""
|
|
330
|
+
# Validate inputs to prevent command injection
|
|
331
|
+
self._validate_env_name(env_name)
|
|
332
|
+
validated_path = self._validate_path(local_path)
|
|
333
|
+
|
|
334
|
+
cmd = [self._get_conda_exe(), "env", "update", "-n", env_name, "--file", validated_path]
|
|
335
|
+
logger.info("Updating conda env: %s", " ".join(cmd))
|
|
336
|
+
_run_shell_cmd(cmd)
|
|
337
|
+
logger.info("Conda env %s updated succesfully", env_name)
|
|
338
|
+
|
|
339
|
+
def _export_conda_env_from_prefix(self, prefix, local_path):
|
|
340
|
+
"""Export the conda env to a conda yml file"""
|
|
341
|
+
# Validate inputs to prevent command injection
|
|
342
|
+
validated_prefix = self._validate_path(prefix)
|
|
343
|
+
validated_path = self._validate_path(local_path)
|
|
344
|
+
|
|
345
|
+
cmd = [self._get_conda_exe(), "env", "export", "-p", validated_prefix, "--no-builds"]
|
|
346
|
+
logger.info("Exporting conda environment: %s", " ".join(cmd))
|
|
347
|
+
|
|
348
|
+
# Capture output and write to file instead of using shell redirection
|
|
349
|
+
try:
|
|
350
|
+
process = subprocess.Popen(
|
|
351
|
+
cmd,
|
|
352
|
+
stdout=subprocess.PIPE,
|
|
353
|
+
stderr=subprocess.PIPE,
|
|
354
|
+
shell=False
|
|
355
|
+
)
|
|
356
|
+
output, error_output = process.communicate()
|
|
357
|
+
return_code = process.wait()
|
|
358
|
+
|
|
359
|
+
if return_code:
|
|
360
|
+
error_message = f"Encountered error while running command '{' '.join(cmd)}'. Reason: {error_output.decode('utf-8')}"
|
|
361
|
+
raise RuntimeEnvironmentError(error_message)
|
|
362
|
+
|
|
363
|
+
# Write the captured output to the file
|
|
364
|
+
with open(validated_path, 'w') as f:
|
|
365
|
+
f.write(output.decode('utf-8'))
|
|
366
|
+
|
|
367
|
+
logger.info("Conda environment %s exported successfully", validated_prefix)
|
|
368
|
+
except Exception as e:
|
|
369
|
+
raise RuntimeEnvironmentError(f"Failed to export conda environment: {str(e)}")
|
|
370
|
+
|
|
371
|
+
def _write_conda_env_to_file(self, env_name):
|
|
372
|
+
"""Writes conda env to the text file"""
|
|
373
|
+
|
|
374
|
+
file_name = "remote_function_conda_env.txt"
|
|
375
|
+
file_path = os.path.join(os.getcwd(), file_name)
|
|
376
|
+
with open(file_path, "w") as output_file:
|
|
377
|
+
output_file.write(env_name)
|
|
378
|
+
|
|
379
|
+
def _get_conda_exe(self):
|
|
380
|
+
"""Checks whether conda or mamba is available to use"""
|
|
381
|
+
|
|
382
|
+
if not subprocess.Popen(["which", "mamba"]).wait():
|
|
383
|
+
return "mamba"
|
|
384
|
+
if not subprocess.Popen(["which", "conda"]).wait():
|
|
385
|
+
return "conda"
|
|
386
|
+
raise ValueError("Neither conda nor mamba is installed on the image")
|
|
387
|
+
|
|
388
|
+
def _python_version_in_conda_env(self, env_name):
|
|
389
|
+
"""Returns python version inside a conda environment"""
|
|
390
|
+
cmd = f"{self._get_conda_exe()} run -n {env_name} python --version"
|
|
391
|
+
try:
|
|
392
|
+
output = (
|
|
393
|
+
subprocess.check_output(shlex.split(cmd), stderr=subprocess.STDOUT)
|
|
394
|
+
.decode("utf-8")
|
|
395
|
+
.strip()
|
|
396
|
+
)
|
|
397
|
+
# convert 'Python 3.7.16' to [3, 7, 16]
|
|
398
|
+
version = output.split("Python ")[1].split(".")
|
|
399
|
+
return version[0] + "." + version[1]
|
|
400
|
+
except subprocess.CalledProcessError as e:
|
|
401
|
+
raise RuntimeEnvironmentError(e.output)
|
|
402
|
+
|
|
403
|
+
def _current_python_version(self):
|
|
404
|
+
"""Returns the current python version where program is running"""
|
|
405
|
+
|
|
406
|
+
return f"{sys.version_info.major}.{sys.version_info.minor}".strip()
|
|
407
|
+
|
|
408
|
+
def _current_sagemaker_pysdk_version(self):
|
|
409
|
+
"""Returns the current sagemaker python sdk version where program is running"""
|
|
410
|
+
try:
|
|
411
|
+
from importlib import metadata
|
|
412
|
+
|
|
413
|
+
return metadata.version("sagemaker")
|
|
414
|
+
except Exception:
|
|
415
|
+
return "3.0.0.dev0" # Development version fallback
|
|
416
|
+
|
|
417
|
+
def _validate_python_version(self, client_python_version: str, conda_env: str = None):
|
|
418
|
+
"""Validate the python version
|
|
419
|
+
|
|
420
|
+
Validates if the python version where remote function runs
|
|
421
|
+
matches the one used on client side.
|
|
422
|
+
"""
|
|
423
|
+
if conda_env:
|
|
424
|
+
job_python_version = self._python_version_in_conda_env(conda_env)
|
|
425
|
+
else:
|
|
426
|
+
job_python_version = self._current_python_version()
|
|
427
|
+
if client_python_version.strip() != job_python_version.strip():
|
|
428
|
+
raise RuntimeEnvironmentError(
|
|
429
|
+
f"Python version found in the container is '{job_python_version}' which "
|
|
430
|
+
f"does not match python version '{client_python_version}' on the local client. "
|
|
431
|
+
f"Please make sure that the python version used in the training container "
|
|
432
|
+
f"is same as the local python version."
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def _validate_sagemaker_pysdk_version(self, client_sagemaker_pysdk_version):
|
|
436
|
+
"""Validate the sagemaker python sdk version
|
|
437
|
+
|
|
438
|
+
Validates if the sagemaker python sdk version where remote function runs
|
|
439
|
+
matches the one used on client side.
|
|
440
|
+
Otherwise, log a warning to call out that unexpected behaviors
|
|
441
|
+
may occur in this case.
|
|
442
|
+
"""
|
|
443
|
+
job_sagemaker_pysdk_version = self._current_sagemaker_pysdk_version()
|
|
444
|
+
if (
|
|
445
|
+
client_sagemaker_pysdk_version
|
|
446
|
+
and client_sagemaker_pysdk_version != job_sagemaker_pysdk_version
|
|
447
|
+
):
|
|
448
|
+
logger.warning(
|
|
449
|
+
"Inconsistent sagemaker versions found: "
|
|
450
|
+
"sagemaker python sdk version found in the container is "
|
|
451
|
+
"'%s' which does not match the '%s' on the local client. "
|
|
452
|
+
"Please make sure that the sagemaker version used in the training container "
|
|
453
|
+
"is the same as the local sagemaker version in case of unexpected behaviors.",
|
|
454
|
+
job_sagemaker_pysdk_version,
|
|
455
|
+
client_sagemaker_pysdk_version,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _run_and_get_output_shell_cmd(cmd: str) -> str:
|
|
460
|
+
"""Run and return the output of the given shell command"""
|
|
461
|
+
return subprocess.check_output(shlex.split(cmd), stderr=subprocess.STDOUT).decode("utf-8")
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _run_pre_execution_command_script(script_path: str):
|
|
465
|
+
"""This method runs a given shell script using subprocess
|
|
466
|
+
|
|
467
|
+
Raises RuntimeEnvironmentError if the shell script fails
|
|
468
|
+
"""
|
|
469
|
+
current_dir = os.path.dirname(script_path)
|
|
470
|
+
|
|
471
|
+
process = subprocess.Popen(
|
|
472
|
+
["/bin/bash", "-eu", script_path],
|
|
473
|
+
stdout=subprocess.PIPE,
|
|
474
|
+
stderr=subprocess.PIPE,
|
|
475
|
+
cwd=current_dir,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
_log_output(process)
|
|
479
|
+
error_logs = _log_error(process)
|
|
480
|
+
return_code = process.wait()
|
|
481
|
+
|
|
482
|
+
return return_code, error_logs
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _run_shell_cmd(cmd: list):
|
|
486
|
+
"""This method runs a given shell command using subprocess
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
cmd (list): Command and arguments as a list (e.g., ['pip', 'install', '-r', 'requirements.txt'])
|
|
490
|
+
|
|
491
|
+
Raises:
|
|
492
|
+
RuntimeEnvironmentError: If the command fails
|
|
493
|
+
ValueError: If cmd is not a list
|
|
494
|
+
"""
|
|
495
|
+
if not isinstance(cmd, list):
|
|
496
|
+
raise ValueError("Command must be a list of arguments for security reasons")
|
|
497
|
+
|
|
498
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
|
|
499
|
+
|
|
500
|
+
_log_output(process)
|
|
501
|
+
error_logs = _log_error(process)
|
|
502
|
+
return_code = process.wait()
|
|
503
|
+
if return_code:
|
|
504
|
+
error_message = f"Encountered error while running command '{' '.join(cmd)}'. Reason: {error_logs}"
|
|
505
|
+
raise RuntimeEnvironmentError(error_message)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _log_output(process):
|
|
509
|
+
"""This method takes in Popen process and logs the output of that process"""
|
|
510
|
+
with process.stdout as pipe:
|
|
511
|
+
for line in iter(pipe.readline, b""):
|
|
512
|
+
logger.info(str(line, "UTF-8"))
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _log_error(process):
|
|
516
|
+
"""This method takes in Popen process and logs the error of that process.
|
|
517
|
+
|
|
518
|
+
Returns those logs as a string
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
error_logs = ""
|
|
522
|
+
with process.stderr as pipe:
|
|
523
|
+
for line in iter(pipe.readline, b""):
|
|
524
|
+
error_str = str(line, "UTF-8")
|
|
525
|
+
if "ERROR:" in error_str:
|
|
526
|
+
logger.error(error_str)
|
|
527
|
+
else:
|
|
528
|
+
logger.warning(error_str)
|
|
529
|
+
error_logs = error_logs + error_str
|
|
530
|
+
|
|
531
|
+
return error_logs
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _python_executable():
|
|
535
|
+
"""Return the real path for the Python executable, if it exists.
|
|
536
|
+
|
|
537
|
+
Return RuntimeEnvironmentError otherwise.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
(str): The real path of the current Python executable.
|
|
541
|
+
"""
|
|
542
|
+
if not sys.executable:
|
|
543
|
+
raise RuntimeEnvironmentError(
|
|
544
|
+
"Failed to retrieve the path for the Python executable binary"
|
|
545
|
+
)
|
|
546
|
+
return sys.executable
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
class RuntimeEnvironmentError(Exception):
|
|
550
|
+
"""The base exception class for bootstrap env excepitons"""
|
|
551
|
+
|
|
552
|
+
def __init__(self, message):
|
|
553
|
+
self.message = message
|
|
554
|
+
super().__init__(self.message)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
"""This is a simple scrip of spark which invokes the pickled remote function"""
|
|
14
|
+
from __future__ import absolute_import
|
|
15
|
+
|
|
16
|
+
from sagemaker.core.remote_function import invoke_function
|
|
17
|
+
|
|
18
|
+
invoke_function.main()
|