diff --git a/sagemaker-core/src/sagemaker/core/resources.py b/sagemaker-core/src/sagemaker/core/resources.py index dc6bca5f63..a12f14f8d9 100644 --- a/sagemaker-core/src/sagemaker/core/resources.py +++ b/sagemaker-core/src/sagemaker/core/resources.py @@ -24,6 +24,7 @@ from rich.status import Status from rich.style import Style from sagemaker.core.shapes import * +from sagemaker.core import shapes from sagemaker.core.helper.pipeline_variable import StrPipeVar from sagemaker.core.utils.code_injection.codec import transform from sagemaker.core.utils.code_injection.constants import Color @@ -199,7 +200,7 @@ class AIBenchmarkJob(Base): creation_time: Optional[datetime.datetime] = Unassigned() start_time: Optional[datetime.datetime] = Unassigned() end_time: Optional[datetime.datetime] = Unassigned() - tags: Optional[List[Tag]] = Unassigned() + tags: Optional[List[shapes.Tag]] = Unassigned() def get_name(self) -> str: attributes = vars(self) @@ -250,7 +251,7 @@ def create( ai_workload_config_identifier: StrPipeVar, role_arn: StrPipeVar, network_config: Optional[AIBenchmarkNetworkConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["AIBenchmarkJob"]: @@ -650,7 +651,7 @@ class AIRecommendationJob(Base): creation_time: Optional[datetime.datetime] = Unassigned() start_time: Optional[datetime.datetime] = Unassigned() end_time: Optional[datetime.datetime] = Unassigned() - tags: Optional[List[Tag]] = Unassigned() + tags: Optional[List[shapes.Tag]] = Unassigned() def get_name(self) -> str: attributes = vars(self) @@ -700,7 +701,7 @@ def create( optimize_model: Optional[bool] = Unassigned(), compute_spec: Optional[AIRecommendationComputeSpec] = Unassigned(), adapter_source: Optional[AIAdapterSource] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["AIRecommendationJob"]: @@ -1085,7 +1086,7 @@ class AIWorkloadConfig(Base): ai_workload_config_arn: Optional[StrPipeVar] = Unassigned() dataset_config: Optional[AIDatasetConfig] = Unassigned() ai_workload_configs: Optional[AIWorkloadConfigs] = Unassigned() - tags: Optional[List[Tag]] = Unassigned() + tags: Optional[List[shapes.Tag]] = Unassigned() creation_time: Optional[datetime.datetime] = Unassigned() def get_name(self) -> str: @@ -1111,7 +1112,7 @@ def create( ai_workload_config_name: StrPipeVar, dataset_config: Optional[AIDatasetConfig] = Unassigned(), ai_workload_configs: Optional[AIWorkloadConfigs] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["AIWorkloadConfig"]: @@ -1429,7 +1430,7 @@ def create( status: Optional[StrPipeVar] = Unassigned(), properties: Optional[Dict[StrPipeVar, StrPipeVar]] = Unassigned(), metadata_properties: Optional[MetadataProperties] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Action"]: @@ -1825,7 +1826,7 @@ def create( inference_specification: Optional[InferenceSpecification] = Unassigned(), validation_specification: Optional[AlgorithmValidationSpecification] = Unassigned(), certify_for_marketplace: Optional[bool] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Algorithm"]: @@ -2261,7 +2262,7 @@ def create( app_name: StrPipeVar, user_profile_name: Optional[Union[StrPipeVar, object]] = Unassigned(), space_name: Optional[Union[StrPipeVar, object]] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), resource_spec: Optional[ResourceSpec] = Unassigned(), recovery_mode: Optional[bool] = Unassigned(), session: Optional[Session] = None, @@ -2716,7 +2717,7 @@ def get_name(self) -> str: def create( cls, app_image_config_name: StrPipeVar, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), kernel_gateway_image_config: Optional[KernelGatewayImageConfig] = Unassigned(), jupyter_lab_app_image_config: Optional[JupyterLabAppImageConfig] = Unassigned(), code_editor_app_image_config: Optional[CodeEditorAppImageConfig] = Unassigned(), @@ -3082,7 +3083,7 @@ def create( artifact_name: Optional[StrPipeVar] = Unassigned(), properties: Optional[Dict[StrPipeVar, StrPipeVar]] = Unassigned(), metadata_properties: Optional[MetadataProperties] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Artifact"]: @@ -3715,7 +3716,7 @@ def create( auto_ml_job_objective: Optional[AutoMLJobObjective] = Unassigned(), auto_ml_job_config: Optional[AutoMLJobConfig] = Unassigned(), generate_candidate_definitions_only: Optional[bool] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), model_deploy_config: Optional[ModelDeployConfig] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -4230,7 +4231,7 @@ def create( output_data_config: AutoMLOutputDataConfig, auto_ml_problem_type_config: AutoMLProblemTypeConfig, role_arn: StrPipeVar, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), security_config: Optional[AutoMLSecurityConfig] = Unassigned(), auto_ml_job_objective: Optional[AutoMLJobObjective] = Unassigned(), model_deploy_config: Optional[ModelDeployConfig] = Unassigned(), @@ -4554,7 +4555,7 @@ def create( ClusterRestrictedInstanceGroupsConfig ] = Unassigned(), vpc_config: Optional[VpcConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), orchestrator: Optional[ClusterOrchestrator] = Unassigned(), node_recovery: Optional[StrPipeVar] = Unassigned(), tiered_storage_config: Optional[ClusterTieredStorageConfig] = Unassigned(), @@ -5315,7 +5316,7 @@ def create( cluster_arn: StrPipeVar, scheduler_config: SchedulerConfig, description: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["ClusterSchedulerConfig"]: @@ -5819,7 +5820,7 @@ def create( cls, code_repository_name: StrPipeVar, git_config: GitConfig, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["CodeRepository"]: @@ -6211,7 +6212,7 @@ def create( model_package_version_arn: Optional[StrPipeVar] = Unassigned(), input_config: Optional[InputConfig] = Unassigned(), vpc_config: Optional[NeoVpcConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["CompilationJob"]: @@ -6640,7 +6641,7 @@ def create( compute_quota_target: ComputeQuotaTarget, description: Optional[StrPipeVar] = Unassigned(), activation_state: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["ComputeQuota"]: @@ -7159,7 +7160,7 @@ def create( context_type: StrPipeVar, description: Optional[StrPipeVar] = Unassigned(), properties: Optional[Dict[StrPipeVar, StrPipeVar]] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Context"]: @@ -7569,7 +7570,7 @@ def create( data_quality_baseline_config: Optional[DataQualityBaselineConfig] = Unassigned(), network_config: Optional[MonitoringNetworkConfig] = Unassigned(), stopping_condition: Optional[MonitoringStoppingCondition] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["DataQualityJobDefinition"]: @@ -8118,7 +8119,7 @@ def create( output_config: EdgeOutputConfig, role_arn: Optional[StrPipeVar] = Unassigned(), description: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), enable_iot_role_alias: Optional[bool] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -8523,7 +8524,7 @@ def get_report( def register_devices( self, devices: List[Device], - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[str] = None, ) -> None: @@ -8758,7 +8759,7 @@ def create( domain_settings: Optional[DomainSettings] = Unassigned(), subnet_ids: Optional[List[StrPipeVar]] = Unassigned(), vpc_id: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), app_network_access_type: Optional[StrPipeVar] = Unassigned(), home_efs_file_system_kms_key_id: Optional[StrPipeVar] = Unassigned(), kms_key_id: Optional[StrPipeVar] = Unassigned(), @@ -9258,7 +9259,7 @@ def create( model_configs: List[EdgeDeploymentModelConfig], device_fleet_name: Union[StrPipeVar, object], stages: Optional[List[DeploymentStage]] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["EdgeDeploymentPlan"]: @@ -9837,7 +9838,7 @@ def create( role_arn: StrPipeVar, output_config: EdgeOutputConfig, resource_key: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["EdgePackagingJob"]: @@ -10261,7 +10262,7 @@ def create( endpoint_name: StrPipeVar, endpoint_config_name: Union[StrPipeVar, object], deployment_config: Optional[DeploymentConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Endpoint"]: @@ -11096,7 +11097,7 @@ def create( endpoint_config_name: StrPipeVar, production_variants: List[ProductionVariant], data_capture_config: Optional[DataCaptureConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), kms_key_id: Optional[StrPipeVar] = Unassigned(), async_inference_config: Optional[AsyncInferenceConfig] = Unassigned(), explainer_config: Optional[ExplainerConfig] = Unassigned(), @@ -11419,7 +11420,7 @@ def create( experiment_name: StrPipeVar, display_name: Optional[StrPipeVar] = Unassigned(), description: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Experiment"]: @@ -11812,7 +11813,7 @@ def create( throughput_config: Optional[ThroughputConfig] = Unassigned(), role_arn: Optional[StrPipeVar] = Unassigned(), description: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["FeatureGroup"]: @@ -12920,7 +12921,7 @@ def create( human_loop_request_source: Optional[HumanLoopRequestSource] = Unassigned(), human_loop_activation_config: Optional[HumanLoopActivationConfig] = Unassigned(), human_loop_config: Optional[HumanLoopConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["FlowDefinition"]: @@ -13367,7 +13368,7 @@ def create( hub_display_name: Optional[StrPipeVar] = Unassigned(), hub_search_keywords: Optional[List[StrPipeVar]] = Unassigned(), s3_storage_config: Optional[HubS3StorageConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Hub"]: @@ -14131,7 +14132,7 @@ def load( hub_content_markdown: Optional[StrPipeVar] = Unassigned(), support_status: Optional[StrPipeVar] = Unassigned(), hub_content_search_keywords: Optional[List[StrPipeVar]] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["HubContent"]: @@ -14414,7 +14415,7 @@ class HubContentReference(Base): hub_content_arn: StrPipeVar hub_content_name: Optional[Union[StrPipeVar, object]] = Unassigned() min_version: Optional[StrPipeVar] = Unassigned() - tags: Optional[List[Tag]] = Unassigned() + tags: Optional[List[shapes.Tag]] = Unassigned() def get_name(self) -> str: attributes = vars(self) @@ -14440,7 +14441,7 @@ def create( sage_maker_public_hub_content_arn: StrPipeVar, hub_content_name: Optional[Union[StrPipeVar, object]] = Unassigned(), min_version: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[str] = None, ) -> Optional["HubContentReference"]: @@ -14627,7 +14628,7 @@ def create( cls, human_task_ui_name: StrPipeVar, ui_template: UiTemplate, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["HumanTaskUi"]: @@ -15089,7 +15090,7 @@ def create( List[HyperParameterTrainingJobDefinition] ] = Unassigned(), warm_start_config: Optional[HyperParameterTuningJobWarmStartConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), autotune: Optional[Autotune] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -15643,7 +15644,7 @@ def create( role_arn: StrPipeVar, description: Optional[StrPipeVar] = Unassigned(), display_name: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Image"]: @@ -16693,7 +16694,7 @@ def create( specification: Optional[InferenceComponentSpecification] = Unassigned(), specifications: Optional[List[InferenceComponentSpecification]] = Unassigned(), runtime_config: Optional[InferenceComponentRuntimeConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["InferenceComponent"]: @@ -17273,7 +17274,7 @@ def create( description: Optional[StrPipeVar] = Unassigned(), data_storage_config: Optional[InferenceExperimentDataStorageConfig] = Unassigned(), kms_key: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["InferenceExperiment"]: @@ -17833,7 +17834,7 @@ def create( job_description: Optional[StrPipeVar] = Unassigned(), stopping_conditions: Optional[RecommendationJobStoppingConditions] = Unassigned(), output_config: Optional[RecommendationJobOutputConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["InferenceRecommendationsJob"]: @@ -18327,7 +18328,7 @@ class Job(Base): secondary_status: Optional[StrPipeVar] = Unassigned() secondary_status_transitions: Optional[List[JobSecondaryStatusTransition]] = Unassigned() failure_reason: Optional[StrPipeVar] = Unassigned() - tags: Optional[List[Tag]] = Unassigned() + tags: Optional[List[shapes.Tag]] = Unassigned() def get_name(self) -> str: attributes = vars(self) @@ -18368,7 +18369,7 @@ def create( job_category: StrPipeVar, job_config_schema_version: StrPipeVar, job_config_document: StrPipeVar, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Job"]: @@ -18840,7 +18841,7 @@ class LabelingJob(Base): stopping_conditions: Optional[LabelingJobStoppingConditions] = Unassigned() labeling_job_algorithms_config: Optional[LabelingJobAlgorithmsConfig] = Unassigned() human_task_config: Optional[HumanTaskConfig] = Unassigned() - tags: Optional[List[Tag]] = Unassigned() + tags: Optional[List[shapes.Tag]] = Unassigned() labeling_job_output: Optional[LabelingJobOutput] = Unassigned() def get_name(self) -> str: @@ -18907,7 +18908,7 @@ def create( label_category_config_s3_uri: Optional[StrPipeVar] = Unassigned(), stopping_conditions: Optional[LabelingJobStoppingConditions] = Unassigned(), labeling_job_algorithms_config: Optional[LabelingJobAlgorithmsConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["LabelingJob"]: @@ -19569,7 +19570,7 @@ def create( weekly_maintenance_window_start: Optional[StrPipeVar] = Unassigned(), account_default_status: Optional[StrPipeVar] = Unassigned(), default_domain_id_list: Optional[List[StrPipeVar]] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["MlflowApp"]: @@ -20116,7 +20117,7 @@ def create( mlflow_version: Optional[StrPipeVar] = Unassigned(), automatic_model_registration: Optional[bool] = Unassigned(), weekly_maintenance_window_start: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), s3_bucket_owner_account_id: Optional[StrPipeVar] = Unassigned(), s3_bucket_owner_verification: Optional[bool] = Unassigned(), session: Optional[Session] = None, @@ -20739,7 +20740,7 @@ def create( containers: Optional[List[ContainerDefinition]] = Unassigned(), inference_execution_config: Optional[InferenceExecutionConfig] = Unassigned(), execution_role_arn: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), vpc_config: Optional[VpcConfig] = Unassigned(), enable_network_isolation: Optional[bool] = Unassigned(), session: Optional[Session] = None, @@ -21149,7 +21150,7 @@ def create( model_bias_baseline_config: Optional[ModelBiasBaselineConfig] = Unassigned(), network_config: Optional[MonitoringNetworkConfig] = Unassigned(), stopping_condition: Optional[MonitoringStoppingCondition] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["ModelBiasJobDefinition"]: @@ -21490,7 +21491,7 @@ def create( content: StrPipeVar, model_card_status: StrPipeVar, security_config: Optional[ModelCardSecurityConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["ModelCard"]: @@ -22389,7 +22390,7 @@ def create( ] = Unassigned(), network_config: Optional[MonitoringNetworkConfig] = Unassigned(), stopping_condition: Optional[MonitoringStoppingCondition] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["ModelExplainabilityJobDefinition"]: @@ -22819,7 +22820,7 @@ def create( validation_specification: Optional[ModelPackageValidationSpecification] = Unassigned(), source_algorithm_specification: Optional[SourceAlgorithmSpecification] = Unassigned(), certify_for_marketplace: Optional[bool] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), model_approval_status: Optional[StrPipeVar] = Unassigned(), metadata_properties: Optional[MetadataProperties] = Unassigned(), model_metrics: Optional[ModelMetrics] = Unassigned(), @@ -23431,7 +23432,7 @@ def create( cls, model_package_group_name: StrPipeVar, model_package_group_description: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), managed_configuration: Optional[ManagedConfiguration] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -24032,7 +24033,7 @@ def create( model_quality_baseline_config: Optional[ModelQualityBaselineConfig] = Unassigned(), network_config: Optional[MonitoringNetworkConfig] = Unassigned(), stopping_condition: Optional[MonitoringStoppingCondition] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["ModelQualityJobDefinition"]: @@ -24747,7 +24748,7 @@ def create( cls, monitoring_schedule_name: StrPipeVar, monitoring_schedule_config: MonitoringScheduleConfig, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["MonitoringSchedule"]: @@ -25298,7 +25299,7 @@ def create( security_group_ids: Optional[List[StrPipeVar]] = Unassigned(), ip_address_type: Optional[StrPipeVar] = Unassigned(), kms_key_id: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), lifecycle_config_name: Optional[StrPipeVar] = Unassigned(), direct_internet_access: Optional[StrPipeVar] = Unassigned(), volume_size_in_gb: Optional[int] = Unassigned(), @@ -25931,7 +25932,7 @@ def create( notebook_instance_lifecycle_config_name: StrPipeVar, on_create: Optional[List[NotebookInstanceLifecycleHook]] = Unassigned(), on_start: Optional[List[NotebookInstanceLifecycleHook]] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["NotebookInstanceLifecycleConfig"]: @@ -26337,7 +26338,7 @@ def create( stopping_condition: StoppingCondition, max_instance_count: Optional[int] = Unassigned(), optimization_environment: Optional[Dict[StrPipeVar, StrPipeVar]] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), vpc_config: Optional[OptimizationVpcConfig] = Unassigned(), training_plan_arns: Optional[List[StrPipeVar]] = Unassigned(), session: Optional[Session] = None, @@ -26812,7 +26813,7 @@ def create( enable_iam_session_based_identity: Optional[bool] = Unassigned(), enable_auto_minor_version_upgrade: Optional[bool] = Unassigned(), client_token: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["PartnerApp"]: @@ -26999,7 +27000,7 @@ def update( enable_auto_minor_version_upgrade: Optional[bool] = Unassigned(), app_version: Optional[StrPipeVar] = Unassigned(), client_token: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), ) -> Optional["PartnerApp"]: """ Update a PartnerApp resource @@ -27421,7 +27422,7 @@ def create( pipeline_definition: Optional[StrPipeVar] = Unassigned(), pipeline_definition_s3_location: Optional[PipelineDefinitionS3Location] = Unassigned(), pipeline_description: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), parallelism_configuration: Optional[ParallelismConfiguration] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -29052,7 +29053,7 @@ def create( stopping_condition: Optional[ProcessingStoppingCondition] = Unassigned(), environment: Optional[Dict[StrPipeVar, StrPipeVar]] = Unassigned(), network_config: Optional[NetworkConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), experiment_config: Optional[ExperimentConfig] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -29507,7 +29508,7 @@ def create( service_catalog_provisioning_details: Optional[ ServiceCatalogProvisioningDetails ] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), template_providers: Optional[List[CreateTemplateProvider]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -29665,7 +29666,7 @@ def update( service_catalog_provisioning_update_details: Optional[ ServiceCatalogProvisioningUpdateDetails ] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), template_providers_to_update: Optional[List[UpdateTemplateProvider]] = Unassigned(), ) -> Optional["Project"]: """ @@ -30153,7 +30154,7 @@ def create( cls, domain_id: StrPipeVar, space_name: StrPipeVar, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), space_settings: Optional[SpaceSettings] = Unassigned(), ownership_settings: Optional[OwnershipSettings] = Unassigned(), space_sharing_settings: Optional[SpaceSharingSettings] = Unassigned(), @@ -30644,7 +30645,7 @@ def create( studio_lifecycle_config_name: StrPipeVar, studio_lifecycle_config_content: StrPipeVar, studio_lifecycle_config_app_type: StrPipeVar, - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["StudioLifecycleConfig"]: @@ -31173,7 +31174,7 @@ def get_all( def add_tags( cls, resource_arn: StrPipeVar, - tags: List[Tag], + tags: List[shapes.Tag], session: Optional[Session] = None, region: Optional[str] = None, ) -> None: @@ -31429,7 +31430,7 @@ def create( resource_config: Optional[ResourceConfig] = Unassigned(), vpc_config: Optional[VpcConfig] = Unassigned(), stopping_condition: Optional[StoppingCondition] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), enable_network_isolation: Optional[bool] = Unassigned(), enable_inter_container_traffic_encryption: Optional[bool] = Unassigned(), enable_managed_spot_training: Optional[bool] = Unassigned(), @@ -32094,7 +32095,7 @@ def create( training_plan_name: StrPipeVar, training_plan_offering_id: StrPipeVar, spare_instance_count_per_ultra_server: Optional[int] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["TrainingPlan"]: @@ -32435,7 +32436,7 @@ def create( output_config: TrainingSessionOutputConfig, model_package_group_arn: Optional[StrPipeVar] = Unassigned(), vpc_config: Optional[VpcConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["TrainingSession"]: @@ -32907,7 +32908,7 @@ def create( environment: Optional[Dict[StrPipeVar, StrPipeVar]] = Unassigned(), data_capture_config: Optional[BatchDataCaptureConfig] = Unassigned(), data_processing: Optional[DataProcessing] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), experiment_config: Optional[ExperimentConfig] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -33327,7 +33328,7 @@ def create( experiment_name: Union[StrPipeVar, object], display_name: Optional[StrPipeVar] = Unassigned(), metadata_properties: Optional[MetadataProperties] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Trial"]: @@ -33704,7 +33705,7 @@ def create( input_artifacts: Optional[Dict[StrPipeVar, TrialComponentArtifact]] = Unassigned(), output_artifacts: Optional[Dict[StrPipeVar, TrialComponentArtifact]] = Unassigned(), metadata_properties: Optional[MetadataProperties] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["TrialComponent"]: @@ -34376,7 +34377,7 @@ def create( user_profile_name: StrPipeVar, single_sign_on_user_identifier: Optional[StrPipeVar] = Unassigned(), single_sign_on_user_value: Optional[StrPipeVar] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), user_settings: Optional[UserSettings] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, @@ -34882,7 +34883,7 @@ def create( cognito_config: Optional[CognitoConfig] = Unassigned(), oidc_config: Optional[OidcConfig] = Unassigned(), source_ip_config: Optional[SourceIpConfig] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), workforce_vpc_config: Optional[WorkforceVpcConfigRequest] = Unassigned(), ip_address_type: Optional[StrPipeVar] = Unassigned(), session: Optional[Session] = None, @@ -35343,7 +35344,7 @@ def create( workforce_name: Optional[Union[StrPipeVar, object]] = Unassigned(), notification_configuration: Optional[NotificationConfiguration] = Unassigned(), worker_access_configuration: Optional[WorkerAccessConfiguration] = Unassigned(), - tags: Optional[List[Tag]] = Unassigned(), + tags: Optional[List[shapes.Tag]] = Unassigned(), session: Optional[Session] = None, region: Optional[StrPipeVar] = None, ) -> Optional["Workteam"]: diff --git a/sagemaker-core/src/sagemaker/core/tools/constants.py b/sagemaker-core/src/sagemaker/core/tools/constants.py index fe07f0f0a4..16f7a8e453 100644 --- a/sagemaker-core/src/sagemaker/core/tools/constants.py +++ b/sagemaker-core/src/sagemaker/core/tools/constants.py @@ -53,6 +53,16 @@ BASIC_RETURN_TYPES = {"str", "int", "bool", "float", "datetime.datetime"} SHAPE_DAG_FILE_PATH = os.getcwd() + "/src/sagemaker/core/utils/code_injection/shape_dag.py" + +# Shape names that a resource class of the same name shadows inside resources.py. +# resources.py star-imports the shapes, then later defines ``class Tag(Base)`` (the +# ListTags resource). An unqualified ``Tag`` annotation written before that class means +# the shape only while annotations are evaluated eagerly; Python 3.14 evaluates them +# lazily (PEP 649), so it resolves to the resource class and rejects shape instances +# (issue #6554). Resource codegen emits these names as ``shapes.``. Other shared +# names (Device, Workteam, ...) are only referenced after their resource class, so they +# already resolve to the resource class on every Python version and are left alone. +RESOURCE_SHADOWED_SHAPE_NAMES = frozenset({"Tag"}) PYTHON_TYPES_TO_BASIC_JSON_TYPES = { "str": "string", "StrPipeVar": "string", diff --git a/sagemaker-core/src/sagemaker/core/tools/resources_codegen.py b/sagemaker-core/src/sagemaker/core/tools/resources_codegen.py index f981001332..d5bcb2f586 100644 --- a/sagemaker-core/src/sagemaker/core/tools/resources_codegen.py +++ b/sagemaker-core/src/sagemaker/core/tools/resources_codegen.py @@ -29,6 +29,7 @@ LOGGER_STRING, CONFIG_SCHEMA_FILE_NAME, PYTHON_TYPES_TO_BASIC_JSON_TYPES, + RESOURCE_SHADOWED_SHAPE_NAMES, CONFIGURABLE_ATTRIBUTE_SUBSTRINGS, RESOURCE_WITH_LOGS, DEFAULT_TIMEOUT_MESSAGE, @@ -154,7 +155,7 @@ def __init__(self, service_json: dict): # Initialize the resources and shapes extractors self.resources_extractor = ResourcesExtractor() - self.shapes_extractor = ShapesExtractor() + self.shapes_extractor = ShapesExtractor(qualified_shape_names=RESOURCE_SHADOWED_SHAPE_NAMES) # Extract the resources plan and shapes DAG self.resources_plan = self.resources_extractor.get_resource_plan() @@ -196,6 +197,9 @@ def generate_imports(self) -> str: "from rich.status import Status", "from rich.style import Style", "from sagemaker.core.shapes import *", + # Qualified access for shape names a resource class shadows (see + # RESOURCE_SHADOWED_SHAPE_NAMES), e.g. ``shapes.Tag`` vs resource ``Tag``. + "from sagemaker.core import shapes", "from sagemaker.core.helper.pipeline_variable import StrPipeVar", "from sagemaker.core.utils.code_injection.codec import transform", "from sagemaker.core.utils.code_injection.constants import Color", diff --git a/sagemaker-core/src/sagemaker/core/tools/shapes_extractor.py b/sagemaker-core/src/sagemaker/core/tools/shapes_extractor.py index fb35676810..86392fedc9 100644 --- a/sagemaker-core/src/sagemaker/core/tools/shapes_extractor.py +++ b/sagemaker-core/src/sagemaker/core/tools/shapes_extractor.py @@ -34,12 +34,20 @@ class ShapesExtractor: """Extracts the shapes to DAG structure.""" - def __init__(self, combined_shapes: Optional[dict] = None): + def __init__( + self, + combined_shapes: Optional[dict] = None, + qualified_shape_names: frozenset = frozenset(), + ): """Initializes a new instance of the ShapesExtractor class. :param combined_shapes: All the shapes put together from all Sagemaker Service JSONs + :param qualified_shape_names: Structure shape names to emit as ``shapes.`` + in type annotations. Used by resource codegen for shapes that a resource + class of the same name shadows in resources.py. Empty for shape codegen. """ self.combined_shapes = combined_shapes or load_combined_shapes_data() + self.qualified_shape_names = qualified_shape_names self.shape_dag = self.get_shapes_dag() with open(SHAPE_DAG_FILE_PATH, "w") as f: @@ -121,6 +129,12 @@ def get_shapes_dag(self): _dag[shape]["value_type"] = _all_shapes[_map_value_shape]["type"] return _dag + def _structure_type(self, shape_name): + """Return the annotation text for a structure shape, qualified when shadowed.""" + if shape_name in self.qualified_shape_names: + return f"shapes.{shape_name}" + return shape_name + def _evaluate_list_type(self, member_shape): list_shape_name = member_shape["member"]["shape"] list_shape_member = self.combined_shapes[list_shape_name] @@ -134,7 +148,7 @@ def _evaluate_list_type(self, member_shape): if list_shape_name == "SearchExpression": member_type = f"List['{list_shape_name}']" else: - member_type = f"List[{list_shape_name}]" + member_type = f"List[{self._structure_type(list_shape_name)}]" elif list_shape_type in BASIC_JSON_TYPES_TO_PYTHON_TYPES.keys(): member_type = f"List[{BASIC_JSON_TYPES_TO_PYTHON_TYPES[list_shape_type]}]" else: @@ -158,7 +172,7 @@ def _evaluate_map_type(self, member_shape): if map_value_shape_type == "structure": member_type = ( f"Dict[{BASIC_JSON_TYPES_TO_PYTHON_TYPES[map_key_shape_type]}, " - f"{map_value_shape_name}]" + f"{self._structure_type(map_value_shape_name)}]" ) elif map_value_shape_type == "list": member_type = ( @@ -238,7 +252,7 @@ def generate_shape_members(self, shape, required_override=()): member_shape = self.combined_shapes[member_shape_name] member_shape_type = member_shape["type"] if member_shape_type == "structure": - member_type = member_shape_name + member_type = self._structure_type(member_shape_name) elif member_shape_type == "list": member_type = self._evaluate_list_type(member_shape) elif member_shape_type == "map": diff --git a/sagemaker-core/tests/unit/generated/test_resources_shadowed_shapes.py b/sagemaker-core/tests/unit/generated/test_resources_shadowed_shapes.py new file mode 100644 index 0000000000..7ade77d512 --- /dev/null +++ b/sagemaker-core/tests/unit/generated/test_resources_shadowed_shapes.py @@ -0,0 +1,102 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Guard resource annotations against shape names shadowed by resource classes. + +resources.py star-imports every shape and then defines resource classes, some of which +share a shape's name (``Tag``). Before Python 3.14 an annotation is evaluated when its +function or class is defined, so a bare ``Tag`` written above ``class Tag`` means the +shape. Python 3.14 evaluates annotations lazily (PEP 649), so the same text resolves to +the resource class and rejects shape instances (issue #6554). +""" + +import ast +import inspect +import typing + +import pytest +from pydantic import TypeAdapter + +from sagemaker.core import resources, shapes + + +def _resource_methods_with_tags(): + for _, cls in inspect.getmembers(resources, inspect.isclass): + if cls.__module__ != resources.__name__: + continue + for method_name in ("create", "add_tags"): + method = cls.__dict__.get(method_name) + if method is None: + continue + func = method.__func__ if isinstance(method, classmethod) else method + func = inspect.unwrap(func) + if "tags" in inspect.signature(func).parameters: + yield f"{cls.__name__}.{method_name}", func + + +TAGGED_METHODS = sorted(_resource_methods_with_tags()) + + +def test_no_shape_name_used_before_its_shadowing_resource_class(): + """A bare annotation name must not change meaning between eager and lazy evaluation.""" + tree = ast.parse(inspect.getsource(resources)) + shape_names = {name for name, obj in vars(shapes).items() if inspect.isclass(obj)} + shadowing_class_line = { + node.name: node.lineno + for node in tree.body + if isinstance(node, ast.ClassDef) and node.name in shape_names and node.name != "Base" + } + + def annotations(node): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + args = node.args + for arg in args.posonlyargs + args.args + args.kwonlyargs: + yield arg.annotation + yield node.returns + elif isinstance(node, ast.AnnAssign): + yield node.annotation + + offenders = set() + for node in ast.walk(tree): + for annotation in annotations(node): + if annotation is None: + continue + for name in ast.walk(annotation): + if ( + isinstance(name, ast.Name) + and name.id in shadowing_class_line + and name.lineno < shadowing_class_line[name.id] + ): + offenders.add((name.id, name.lineno)) + + assert not offenders, ( + "Annotations reference a shape name that a later resource class shadows; add the " + f"name to RESOURCE_SHADOWED_SHAPE_NAMES and regenerate: {sorted(offenders)[:10]}" + ) + + +def test_tagged_methods_found(): + assert len(TAGGED_METHODS) > 50 + assert "HyperParameterTuningJob.create" in dict(TAGGED_METHODS) + + +@pytest.mark.parametrize("name,func", TAGGED_METHODS, ids=[name for name, _ in TAGGED_METHODS]) +def test_tags_annotation_resolves_to_shape_tag(name, func): + """Every generated ``tags`` parameter takes the shape Tag on every Python version.""" + tags_type = typing.get_type_hints(func)["tags"] + adapter = TypeAdapter(tags_type) + + validated = adapter.validate_python([shapes.Tag(key="project", value="p1")]) + assert type(validated[0]) is shapes.Tag + + validated = adapter.validate_python([{"key": "project", "value": "p1"}]) + assert type(validated[0]) is shapes.Tag diff --git a/sagemaker-core/tests/unit/tools/test_shapes_extractor.py b/sagemaker-core/tests/unit/tools/test_shapes_extractor.py index 84cebacee7..35b1b28b97 100644 --- a/sagemaker-core/tests/unit/tools/test_shapes_extractor.py +++ b/sagemaker-core/tests/unit/tools/test_shapes_extractor.py @@ -492,3 +492,48 @@ def test_other_members_stay_required(self, extractor): assert members["capture_status"] == "StrPipeVar" assert members["current_sampling_percentage"] == "int" assert members["destination_s3_uri"] == "StrPipeVar" + + +class TestQualifiedShapeNames: + """Shapes that a resource class shadows are emitted as ``shapes.`` (issue #6554).""" + + _SHAPES = { + "Holder": { + "type": "structure", + "members": { + "Tag": {"shape": "Tag"}, + "Tags": {"shape": "TagList"}, + "TagMap": {"shape": "TagMap"}, + "Other": {"shape": "Other"}, + }, + }, + "Tag": {"type": "structure", "members": {}}, + "Other": {"type": "structure", "members": {}}, + "TagList": {"type": "list", "member": {"shape": "Tag"}}, + "TagMap": {"type": "map", "key": {"shape": "String"}, "value": {"shape": "Tag"}}, + "String": {"type": "string"}, + } + + @patch("sagemaker.core.tools.shapes_extractor.reformat_file_with_black") + def test_qualifies_only_listed_shapes(self, mock_reformat): + with patch("builtins.open", create=True): + extractor = ShapesExtractor( + combined_shapes=self._SHAPES, qualified_shape_names=frozenset({"Tag"}) + ) + + members = extractor.generate_shape_members("Holder") + + assert members["tag"] == "Optional[shapes.Tag] = Unassigned()" + assert members["tags"] == "Optional[List[shapes.Tag]] = Unassigned()" + assert members["tag_map"] == "Optional[Dict[StrPipeVar, shapes.Tag]] = Unassigned()" + assert members["other"] == "Optional[Other] = Unassigned()" + + @patch("sagemaker.core.tools.shapes_extractor.reformat_file_with_black") + def test_default_leaves_names_unqualified(self, mock_reformat): + """Shape codegen (no qualified names) must keep emitting bare names.""" + with patch("builtins.open", create=True): + extractor = ShapesExtractor(combined_shapes=self._SHAPES) + + members = extractor.generate_shape_members("Holder") + + assert members["tags"] == "Optional[List[Tag]] = Unassigned()" diff --git a/sagemaker-train/src/sagemaker/train/tuner.py b/sagemaker-train/src/sagemaker/train/tuner.py index f906c83317..652b71511e 100644 --- a/sagemaker-train/src/sagemaker/train/tuner.py +++ b/sagemaker-train/src/sagemaker/train/tuner.py @@ -577,8 +577,16 @@ def _build_driver_and_code_channels(cls, model_trainer): import tempfile session = model_trainer.sagemaker_session - bucket = session.default_bucket() - s3_key = f"{key_prefix}/sourcedir/sourcedir.tar.gz" + # Resolve bucket and prefix the same way as create_input_data_channel, so the + # source lands next to sm_drivers and honors default_bucket_prefix and the + # output-path fallback when the role can't write the default bucket (#6555). + source_key_prefix = f"{key_prefix}/sourcedir" + if session.default_bucket_prefix: + source_key_prefix = f"{session.default_bucket_prefix}/{source_key_prefix}" + bucket, staging_prefix = model_trainer._resolve_staging_bucket() + if staging_prefix: + source_key_prefix = f"{staging_prefix}/{source_key_prefix}" + s3_key = f"{source_key_prefix}/sourcedir.tar.gz" with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) as tmp: tar_path = tmp.name diff --git a/sagemaker-train/tests/unit/train/test_model_trainer.py b/sagemaker-train/tests/unit/train/test_model_trainer.py index 2bb0e6246f..404a4a7406 100644 --- a/sagemaker-train/tests/unit/train/test_model_trainer.py +++ b/sagemaker-train/tests/unit/train/test_model_trainer.py @@ -2795,10 +2795,9 @@ def test_boto3_request_uses_boto_style_tag_keys(model_trainer): def test_interactive_request_keeps_tags_training_job_create_accepts(model_trainer): """The interactive request carries dicts, not Tag models. - ``TrainingJob.create`` annotates ``tags`` with ``sagemaker.core.resources.Tag``, - which shadows the shape of the same name, so a ``sagemaker.core.shapes.Tag`` - instance fails its validation. The dicts are revalidated into that model instead, - and come out boto-style on the wire. + ``TrainingJob.create`` validates ``tags`` as ``sagemaker.core.shapes.Tag`` (it used + to resolve to the shadowing ``sagemaker.core.resources.Tag``, see issue #6554). The + dicts are revalidated into that model and come out boto-style on the wire. """ model_trainer.tags = [TAG] diff --git a/sagemaker-train/tests/unit/train/test_tuner_driver_channels.py b/sagemaker-train/tests/unit/train/test_tuner_driver_channels.py index 1b71aa8b06..60cdd7009f 100644 --- a/sagemaker-train/tests/unit/train/test_tuner_driver_channels.py +++ b/sagemaker-train/tests/unit/train/test_tuner_driver_channels.py @@ -89,6 +89,7 @@ def _mock_model_trainer(**overrides): trainer.tags = [] trainer.source_code = None trainer.configure_mock(**{"networking": None}) + trainer._resolve_staging_bucket.return_value = ("my-bucket", None) for k, v in overrides.items(): setattr(trainer, k, v) return trainer @@ -316,6 +317,33 @@ def test_uploads_sourcedir_without_kms_key(self, tmp_path): assert mock_s3_client.upload_file.call_args.kwargs["ExtraArgs"] is None + def test_uploads_sourcedir_to_resolved_staging_bucket(self, tmp_path): + """Source upload must follow the trainer's staging-bucket fallback (issue #6555).""" + trainer, mock_s3_client = self._upload_trainer(tmp_path, None) + trainer._resolve_staging_bucket.return_value = ("output-bucket", "jobs") + + HyperparameterTuner._build_driver_and_code_channels(trainer) + + trainer._resolve_staging_bucket.assert_called_once_with() + trainer.sagemaker_session.default_bucket.assert_not_called() + _, bucket, key = mock_s3_client.upload_file.call_args.args + assert bucket == "output-bucket" + assert key.startswith("jobs/test-tuning/") + assert key.endswith("/input/sourcedir/sourcedir.tar.gz") + assert trainer.hyperparameters["sagemaker_submit_directory"] == f"s3://output-bucket/{key}" + + def test_uploads_sourcedir_with_default_bucket_prefix(self, tmp_path): + """Source upload must honor the session's default_bucket_prefix (issue #6555).""" + trainer, mock_s3_client = self._upload_trainer(tmp_path, None) + trainer.sagemaker_session.default_bucket_prefix = "team-prefix" + + HyperparameterTuner._build_driver_and_code_channels(trainer) + + _, bucket, key = mock_s3_client.upload_file.call_args.args + assert bucket == "my-bucket" + assert key.startswith("team-prefix/test-tuning/") + assert key.endswith("/sourcedir/sourcedir.tar.gz") + def test_sets_submit_directory_for_s3_source_dir(self, tmp_path): """Should set sagemaker_submit_directory directly when source_dir is an S3 URI.""" source_code = _make_source_code(source_dir="s3://my-bucket/code/sourcedir.tar.gz")