Files
2026-07-13 13:22:34 +08:00

1091 lines
40 KiB
Python

import json
import time
from datetime import datetime
from typing import Any, NamedTuple
from moto.core import DEFAULT_ACCOUNT_ID, BackendDict, BaseBackend, BaseModel
from moto.core.models import base_decorator
from moto.core.responses import BaseResponse
class SageMakerResourceWithArn(NamedTuple):
resource: Any
arn: str
class SageMakerResponse(BaseResponse):
"""
A collection of handlers for SageMaker API calls that produce API-conforming
JSON responses.
"""
@property
def sagemaker_backend(self):
return sagemaker_backends[DEFAULT_ACCOUNT_ID][self.region]
@property
def request_params(self):
return json.loads(self.body)
def create_endpoint_config(self):
"""
Handler for the SageMaker "CreateEndpointConfig" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_CreateEndpointConfig.html.
"""
config_name = self.request_params["EndpointConfigName"]
production_variants = self.request_params.get("ProductionVariants")
tags = self.request_params.get("Tags", [])
async_inference_config = self.request_params.get("AsyncInferenceConfig")
new_config = self.sagemaker_backend.create_endpoint_config(
config_name=config_name,
production_variants=production_variants,
tags=tags,
region_name=self.region,
async_inference_config=async_inference_config,
)
return json.dumps({"EndpointConfigArn": new_config.arn})
def describe_endpoint_config(self):
"""
Handler for the SageMaker "DescribeEndpoint" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeEndpoint.html.
"""
config_name = self.request_params["EndpointConfigName"]
config_description = self.sagemaker_backend.describe_endpoint_config(config_name)
return json.dumps(config_description.response_object)
def delete_endpoint_config(self):
"""
Handler for the SageMaker "DeleteEndpointConfig" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DeleteEndpointConfig.html.
"""
config_name = self.request_params["EndpointConfigName"]
self.sagemaker_backend.delete_endpoint_config(config_name)
return ""
def create_endpoint(self):
"""
Handler for the SageMaker "CreateEndpoint" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_CreateEndpoint.html.
"""
endpoint_name = self.request_params["EndpointName"]
endpoint_config_name = self.request_params["EndpointConfigName"]
tags = self.request_params.get("Tags", [])
new_endpoint = self.sagemaker_backend.create_endpoint(
endpoint_name=endpoint_name,
endpoint_config_name=endpoint_config_name,
tags=tags,
region_name=self.region,
)
return json.dumps({"EndpointArn": new_endpoint.arn})
def describe_endpoint(self):
"""
Handler for the SageMaker "DescribeEndpoint" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeEndpoint.html.
"""
endpoint_name = self.request_params["EndpointName"]
endpoint_description = self.sagemaker_backend.describe_endpoint(endpoint_name)
return json.dumps(endpoint_description.response_object)
def update_endpoint(self):
"""
Handler for the SageMaker "UpdateEndpoint" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_UpdateEndpoint.html.
"""
endpoint_name = self.request_params["EndpointName"]
new_config_name = self.request_params["EndpointConfigName"]
updated_endpoint = self.sagemaker_backend.update_endpoint(
endpoint_name=endpoint_name, new_config_name=new_config_name
)
return json.dumps({"EndpointArn": updated_endpoint.arn})
def delete_endpoint(self):
"""
Handler for the SageMaker "DeleteEndpoint" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DeleteEndpoint.html.
"""
endpoint_name = self.request_params["EndpointName"]
self.sagemaker_backend.delete_endpoint(endpoint_name)
return ""
def list_endpoints(self):
"""
Handler for the SageMaker "ListEndpoints" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListEndpoints.html.
This function does not support pagination. All endpoint configs are returned in a
single response.
"""
endpoint_summaries = self.sagemaker_backend.list_endpoints()
return json.dumps({
"Endpoints": [summary.response_object for summary in endpoint_summaries]
})
def list_endpoint_configs(self):
"""
Handler for the SageMaker "ListEndpointConfigs" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListEndpointConfigs.html.
This function does not support pagination. All endpoint configs are returned in a
single response.
"""
# Note:
endpoint_config_summaries = self.sagemaker_backend.list_endpoint_configs()
return json.dumps({
"EndpointConfigs": [summary.response_object for summary in endpoint_config_summaries]
})
def list_models(self):
"""
Handler for the SageMaker "ListModels" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListModels.html.
This function does not support pagination. All endpoint configs are returned in a
single response.
"""
model_summaries = self.sagemaker_backend.list_models()
return json.dumps({"Models": [summary.response_object for summary in model_summaries]})
def create_model(self):
"""
Handler for the SageMaker "CreateModel" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_CreateModel.html.
"""
model_name = self.request_params["ModelName"]
primary_container = self.request_params["PrimaryContainer"]
execution_role_arn = self.request_params["ExecutionRoleArn"]
tags = self.request_params.get("Tags", [])
vpc_config = self.request_params.get("VpcConfig", None)
new_model = self.sagemaker_backend.create_model(
model_name=model_name,
primary_container=primary_container,
execution_role_arn=execution_role_arn,
tags=tags,
vpc_config=vpc_config,
region_name=self.region,
)
return json.dumps({"ModelArn": new_model.arn})
def describe_model(self):
"""
Handler for the SageMaker "DescribeModel" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeModel.html.
"""
model_name = self.request_params["ModelName"]
model_description = self.sagemaker_backend.describe_model(model_name)
return json.dumps(model_description.response_object)
def delete_model(self):
"""
Handler for the SageMaker "DeleteModel" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DeleteModel.html.
"""
model_name = self.request_params["ModelName"]
self.sagemaker_backend.delete_model(model_name)
return ""
def list_tags(self):
"""
Handler for the SageMaker "ListTags" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ListTags.html
"""
arn = self.request_params["ResourceArn"]
sagemaker_resource = (
"models" if "model" in arn else "endpoints" if "endpoint" in arn else None
)
results = self.sagemaker_backend.list_tags(
resource_arn=arn, region_name=self.region, resource_type=sagemaker_resource
)
return json.dumps({"Tags": results, "NextToken": None})
def create_transform_job(self):
"""
Handler for the SageMaker "CreateTransformJob" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateTransformJob.html.
"""
job_name = self.request_params["TransformJobName"]
model_name = self.request_params.get("ModelName")
transform_input = self.request_params.get("TransformInput")
transform_output = self.request_params.get("TransformOutput")
transform_resources = self.request_params.get("TransformResources")
data_processing = self.request_params.get("DataProcessing")
tags = self.request_params.get("Tags", [])
new_job = self.sagemaker_backend.create_transform_job(
job_name=job_name,
model_name=model_name,
transform_input=transform_input,
transform_output=transform_output,
transform_resources=transform_resources,
data_processing=data_processing,
tags=tags,
region_name=self.region,
)
return json.dumps({"TransformJobArn": new_job.arn})
def stop_transform_job(self):
"""
Handler for the SageMaker "StopTransformJob" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_StopTransformJob.html.
"""
job_name = self.request_params["TransformJobName"]
self.sagemaker_backend.stop_transform_job(job_name)
return ""
def describe_transform_job(self):
"""
Handler for the SageMaker "DescribeTransformJob" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeTransformJob.html.
"""
job_name = self.request_params["TransformJobName"]
transform_job_description = self.sagemaker_backend.describe_transform_job(job_name)
return json.dumps(transform_job_description.response_object)
def list_transform_jobs(self):
"""
Handler for the SageMaker "ListTransformJobs" API call documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ListTransformJobs.html.
This function does not support pagination. All transform jobs are returned in a
single response.
"""
transform_job_summaries = self.sagemaker_backend.list_transform_jobs()
return json.dumps({
"TransformJobSummaries": [
summary.response_object for summary in transform_job_summaries
]
})
class SageMakerBackend(BaseBackend):
"""
A mock backend for managing and exposing SageMaker resource state.
"""
BASE_SAGEMAKER_ARN = "arn:aws:sagemaker:{region_name}:{account_id}:"
def __init__(self, region_name, account_id=None):
super().__init__(region_name, account_id)
self.models = {}
self.endpoints = {}
self.endpoint_configs = {}
self.transform_jobs = {}
self._endpoint_update_latency_seconds = 0
self._transform_job_update_latency_seconds = 0
def set_endpoint_update_latency(self, latency_seconds):
"""
Sets the latency for the following operations that update endpoint state:
- "create_endpoint"
- "update_endpoint"
"""
self._endpoint_update_latency_seconds = latency_seconds
def set_transform_job_update_latency(self, latency_seconds):
"""
Sets the latency for the following operations that update transform job state:
- "create_transform_job"
- "terminate_transform_job"
"""
self._transform_job_update_latency_seconds = latency_seconds
def set_endpoint_latest_operation(self, endpoint_name, operation):
if endpoint_name not in self.endpoints:
raise ValueError(
"Attempted to manually set the latest operation for an endpoint"
" that does not exist!"
)
self.endpoints[endpoint_name].resource.latest_operation = operation
def set_transform_job_latest_operation(self, transform_job_name, operation):
if transform_job_name not in self.transform_jobs:
raise ValueError(
"Attempted to manually set the latest operation for a transform job"
" that does not exist!"
)
self.transform_jobs[transform_job_name].resource.latest_operation = operation
@property
def _url_module(self):
"""
Required override from the Moto "BaseBackend" object that reroutes requests from the
specified SageMaker URLs to the mocked SageMaker backend.
"""
urls_module_name = "tests.sagemaker.mock.mock_sagemaker_urls"
return __import__(urls_module_name, fromlist=["url_bases", "url_paths"])
def _get_base_arn(self, region_name):
"""
Returns:
A SageMaker ARN prefix that can be prepended to a resource name.
"""
return SageMakerBackend.BASE_SAGEMAKER_ARN.format(
region_name=region_name, account_id=DEFAULT_ACCOUNT_ID
)
def create_endpoint_config(
self, config_name, production_variants, tags, region_name, async_inference_config
):
"""
Modifies backend state during calls to the SageMaker "CreateEndpointConfig" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_CreateEndpointConfig.html.
"""
if config_name in self.endpoint_configs:
raise ValueError(
"Attempted to create an endpoint configuration with name:"
f" {config_name}, but an endpoint configuration with this"
" name already exists."
)
for production_variant in production_variants:
if "ModelName" not in production_variant:
raise ValueError("Production variant must specify a model name.")
elif production_variant["ModelName"] not in self.models:
raise ValueError(
"Production variant specifies a model name that does not exist"
" Model name: '{model_name}'".format(model_name=production_variant["ModelName"])
)
new_config = EndpointConfig(
config_name=config_name,
production_variants=production_variants,
tags=tags,
async_inference_config=async_inference_config,
)
new_config_arn = self._get_base_arn(region_name=region_name) + new_config.arn_descriptor
new_resource = SageMakerResourceWithArn(resource=new_config, arn=new_config_arn)
self.endpoint_configs[config_name] = new_resource
return new_resource
def describe_endpoint_config(self, config_name):
"""
Modifies backend state during calls to the SageMaker "DescribeEndpointConfig" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeEndpointConfig.html.
"""
if config_name not in self.endpoint_configs:
raise ValueError(
f"Attempted to describe an endpoint config with name: `{config_name}`"
" that does not exist."
)
config = self.endpoint_configs[config_name]
return EndpointConfigDescription(config=config.resource, arn=config.arn)
def delete_endpoint_config(self, config_name):
"""
Modifies backend state during calls to the SageMaker "DeleteEndpointConfig" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DeleteEndpointConfig.html.
"""
if config_name not in self.endpoint_configs:
raise ValueError(
f"Attempted to delete an endpoint config with name: `{config_name}`"
" that does not exist."
)
del self.endpoint_configs[config_name]
def create_endpoint(self, endpoint_name, endpoint_config_name, tags, region_name):
"""
Modifies backend state during calls to the SageMaker "CreateEndpoint" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_CreateEndpoint.html.
"""
if endpoint_name in self.endpoints:
raise ValueError(
f"Attempted to create an endpoint with name: `{endpoint_name}`"
" but an endpoint with this name already exists."
)
if endpoint_config_name not in self.endpoint_configs:
raise ValueError(
"Attempted to create an endpoint with a configuration named:"
f" `{endpoint_config_name}` However, this configuration does not exist."
)
new_endpoint = Endpoint(
endpoint_name=endpoint_name,
config_name=endpoint_config_name,
tags=tags,
latest_operation=EndpointOperation.create_successful(
latency_seconds=self._endpoint_update_latency_seconds
),
)
new_endpoint_arn = self._get_base_arn(region_name=region_name) + new_endpoint.arn_descriptor
new_resource = SageMakerResourceWithArn(resource=new_endpoint, arn=new_endpoint_arn)
self.endpoints[endpoint_name] = new_resource
return new_resource
def describe_endpoint(self, endpoint_name):
"""
Modifies backend state during calls to the SageMaker "DescribeEndpoint" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeEndpoint.html.
"""
if endpoint_name not in self.endpoints:
raise ValueError(
f"Attempted to describe an endpoint with name: `{endpoint_name}`"
" that does not exist."
)
endpoint = self.endpoints[endpoint_name]
config = self.endpoint_configs[endpoint.resource.config_name]
return EndpointDescription(
endpoint=endpoint.resource, config=config.resource, arn=endpoint.arn
)
def update_endpoint(self, endpoint_name, new_config_name):
"""
Modifies backend state during calls to the SageMaker "UpdateEndpoint" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_UpdateEndpoint.html.
"""
if endpoint_name not in self.endpoints:
raise ValueError(
f"Attempted to update an endpoint with name: `{endpoint_name}` that does not exist."
)
if new_config_name not in self.endpoint_configs:
raise ValueError(
f"Attempted to update an endpoint named `{endpoint_name}` with a new"
f" configuration named: `{new_config_name}`. However, this configuration"
" does not exist."
)
endpoint = self.endpoints[endpoint_name]
endpoint.resource.latest_operation = EndpointOperation.update_successful(
latency_seconds=self._endpoint_update_latency_seconds
)
endpoint.resource.config_name = new_config_name
return endpoint
def delete_endpoint(self, endpoint_name):
"""
Modifies backend state during calls to the SageMaker "DeleteEndpoint" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DeleteEndpoint.html.
"""
if endpoint_name not in self.endpoints:
raise ValueError(
f"Attempted to delete an endpoint with name: `{endpoint_name}` that does not exist."
)
del self.endpoints[endpoint_name]
def list_endpoints(self):
"""
Modifies backend state during calls to the SageMaker "ListEndpoints" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListEndpoints.html.
"""
return [
EndpointSummary(endpoint=endpoint.resource, arn=endpoint.arn)
for endpoint in self.endpoints.values()
]
def list_endpoint_configs(self):
"""
Modifies backend state during calls to the SageMaker "ListEndpointConfigs" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListEndpointConfigs.html.
"""
return [
EndpointConfigSummary(config=endpoint_config.resource, arn=endpoint_config.arn)
for endpoint_config in self.endpoint_configs.values()
]
def list_models(self):
"""
Modifies backend state during calls to the SageMaker "ListModels" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListModels.html.
"""
return [ModelSummary(model=model.resource, arn=model.arn) for model in self.models.values()]
def list_tags(self, resource_arn, region_name, resource_type):
"""
Modifies backend state during calls to the SageMaker "ListTags" API
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ListTags.html
"""
resource_values = getattr(self, resource_type).values()
for sagemaker_resource in resource_values:
if sagemaker_resource.arn == resource_arn:
return sagemaker_resource.resource.tags
def create_model(
self, model_name, primary_container, execution_role_arn, tags, region_name, vpc_config=None
):
"""
Modifies backend state during calls to the SageMaker "CreateModel" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_CreateModel.html.
"""
if model_name in self.models:
raise ValueError(
f"Attempted to create a model with name: `{model_name}`"
" but a model with this name already exists."
)
new_model = Model(
model_name=model_name,
primary_container=primary_container,
execution_role_arn=execution_role_arn,
tags=tags,
vpc_config=vpc_config,
)
new_model_arn = self._get_base_arn(region_name=region_name) + new_model.arn_descriptor
new_resource = SageMakerResourceWithArn(resource=new_model, arn=new_model_arn)
self.models[model_name] = new_resource
return new_resource
def describe_model(self, model_name):
"""
Modifies backend state during calls to the SageMaker "DescribeModel" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeModel.html.
"""
if model_name not in self.models:
raise ValueError(
f"Attempted to describe a model with name: `{model_name}` that does not exist."
)
model = self.models[model_name]
return ModelDescription(model=model.resource, arn=model.arn)
def delete_model(self, model_name):
"""
Modifies backend state during calls to the SageMaker "DeleteModel" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DeleteModel.html.
"""
if model_name not in self.models:
raise ValueError(
f"Attempted to delete an model with name: `{model_name}` that does not exist."
)
del self.models[model_name]
def create_transform_job(
self,
job_name,
model_name,
transform_input,
transform_output,
transform_resources,
data_processing,
tags,
region_name,
):
"""
Modifies backend state during calls to the SageMaker "CreateTransformJob" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateTransformJob.html.
"""
if job_name in self.transform_jobs:
raise ValueError(
"Attempted to create a transform job with name:"
f" {job_name}, but a transform job with this"
" name already exists."
)
if model_name not in self.models:
raise ValueError(
"Attempted to create a transform job with a model named:"
f" `{model_name}` However, this model does not exist."
)
new_job = TransformJob(
job_name=job_name,
model_name=model_name,
transform_input=transform_input,
transform_output=transform_output,
transform_resources=transform_resources,
data_processing=data_processing,
tags=tags,
latest_operation=TransformJobOperation.create_successful(
latency_seconds=self._transform_job_update_latency_seconds
),
)
new_job_arn = self._get_base_arn(region_name=region_name) + new_job.arn_descriptor
new_resource = SageMakerResourceWithArn(resource=new_job, arn=new_job_arn)
self.transform_jobs[job_name] = new_resource
return new_resource
def describe_transform_job(self, job_name):
"""
Modifies backend state during calls to the SageMaker "DescribeTransformJob" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeTransformJob.html.
"""
if job_name not in self.transform_jobs:
raise ValueError(
f"Attempted to describe a transform job with name: `{job_name}`"
" that does not exist."
)
transform_job = self.transform_jobs[job_name]
return TransformJobDescription(transform_job=transform_job.resource, arn=transform_job.arn)
def stop_transform_job(self, job_name):
"""
Modifies backend state during calls to the SageMaker "StopTransformJob" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_StopTransformJob.html.
"""
if job_name not in self.transform_jobs:
raise ValueError(
f"Attempted to stop a transform job with name: `{job_name}` that does not exist."
)
self.transform_jobs[
job_name
].resource.latest_operation = TransformJobOperation.stop_successful(
latency_seconds=self._transform_job_update_latency_seconds
)
def list_transform_jobs(self):
"""
Modifies backend state during calls to the SageMaker "ListTransformJobs" API
documented here:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ListTransformJobs.html.
"""
return [
TransformJobSummary(transform_job=transform_job.resource, arn=transform_job.arn)
for transform_job in self.transform_jobs.values()
]
class TimestampedResource(BaseModel):
TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
def __init__(self):
curr_time = datetime.now().strftime(TimestampedResource.TIMESTAMP_FORMAT)
self.creation_time = curr_time
self.last_modified_time = curr_time
class Endpoint(TimestampedResource):
"""
Object representing a SageMaker endpoint. The SageMakerBackend will create
and manage Endpoints.
"""
STATUS_IN_SERVICE = "InService"
STATUS_FAILED = "Failed"
STATUS_CREATING = "Creating"
STATUS_UPDATING = "Updating"
def __init__(self, endpoint_name, config_name, tags, latest_operation):
"""
Args:
endpoint_name: The name of the Endpoint.
config_name: The name of the EndpointConfiguration to associate with the Endpoint.
tags: Arbitrary tags to associate with the endpoint.
latest_operation: The most recent operation that was invoked on the endpoint,
represented as an EndpointOperation object.
"""
super().__init__()
self.endpoint_name = endpoint_name
self.config_name = config_name
self.tags = tags
self.latest_operation = latest_operation
@property
def arn_descriptor(self):
return f":endpoint/{self.endpoint_name}"
@property
def status(self):
return self.latest_operation.status()
class TransformJob(TimestampedResource):
"""
Object representing a SageMaker transform job. The SageMakerBackend will create
and manage transform jobs.
"""
STATUS_IN_PROGRESS = "InProgress"
STATUS_FAILED = "Failed"
STATUS_COMPLETED = "Completed"
STATUS_STOPPING = "Stopping"
STATUS_STOPPED = "Stopped"
def __init__(
self,
job_name,
model_name,
transform_input,
transform_output,
transform_resources,
data_processing,
tags,
latest_operation,
):
"""
Args:
job_name: The name of the TransformJob.
model_name: The name of the model to associate with the TransformJob.
transform_input: The input data source and the way transform job consumes it.
transform_output: The output results of the transform job.
transform_resources: The ML instance types and instance count to use for the
transform job.
data_processing: The data structure to specify the inference data and associate data
to the prediction results.
tags: Arbitrary tags to associate with the transform job.
latest_operation: The most recent operation that was invoked on the transform job,
represented as an TransformJobOperation object.
"""
super().__init__()
self.job_name = job_name
self.model_name = model_name
self.transform_input = transform_input
self.transform_output = transform_output
self.transform_resources = transform_resources
self.data_processing = data_processing
self.tags = tags
self.latest_operation = latest_operation
@property
def arn_descriptor(self):
return f":transform-job/{self.job_name}"
@property
def status(self):
return self.latest_operation.status()
class EndpointOperation:
"""
Object representing a SageMaker endpoint operation ("create" or "update"). Every
Endpoint is associated with the operation that was most recently invoked on it.
"""
def __init__(self, latency_seconds, pending_status, completed_status):
"""
Args:
latency_seconds: The latency of the operation, in seconds. Before the time window
specified by this latency elapses, the operation will have the status specified by
``pending_status``. After the time window elapses, the operation will
have the status specified by ``completed_status``.
pending_status: The status that the operation should reflect *before* the latency
window has elapsed.
completed_status: The status that the operation should reflect *after* the latency
window has elapsed.
"""
self.latency_seconds = latency_seconds
self.pending_status = pending_status
self.completed_status = completed_status
self.start_time = time.time()
def status(self):
if time.time() - self.start_time < self.latency_seconds:
return self.pending_status
else:
return self.completed_status
@classmethod
def create_successful(cls, latency_seconds):
return cls(
latency_seconds=latency_seconds,
pending_status=Endpoint.STATUS_CREATING,
completed_status=Endpoint.STATUS_IN_SERVICE,
)
@classmethod
def create_unsuccessful(cls, latency_seconds):
return cls(
latency_seconds=latency_seconds,
pending_status=Endpoint.STATUS_CREATING,
completed_status=Endpoint.STATUS_FAILED,
)
@classmethod
def update_successful(cls, latency_seconds):
return cls(
latency_seconds=latency_seconds,
pending_status=Endpoint.STATUS_UPDATING,
completed_status=Endpoint.STATUS_IN_SERVICE,
)
@classmethod
def update_unsuccessful(cls, latency_seconds):
return cls(
latency_seconds=latency_seconds,
pending_status=Endpoint.STATUS_UPDATING,
completed_status=Endpoint.STATUS_FAILED,
)
class TransformJobOperation:
"""
Object representing a SageMaker transform job operation ("create" or "stop"). Every
transform job is associated with the operation that was most recently invoked on it.
"""
def __init__(self, latency_seconds, pending_status, completed_status):
"""
Args:
latency_seconds: The latency of the operation, in seconds. Before the time window
specified by this latency elapses, the operation will have the status
specified by ``pending_status``. After the time window elapses, the
operation will have the status specified by ``completed_status``.
pending_status: The status that the operation should reflect *before* the latency
window has elapsed.
completed_status: The status that the operation should reflect *after* the latency
window has elapsed.
"""
self.latency_seconds = latency_seconds
self.pending_status = pending_status
self.completed_status = completed_status
self.start_time = time.time()
def status(self):
if time.time() - self.start_time < self.latency_seconds:
return self.pending_status
else:
return self.completed_status
@classmethod
def create_successful(cls, latency_seconds):
return cls(
latency_seconds=latency_seconds,
pending_status=TransformJob.STATUS_IN_PROGRESS,
completed_status=TransformJob.STATUS_COMPLETED,
)
@classmethod
def create_unsuccessful(cls, latency_seconds):
return cls(
latency_seconds=latency_seconds,
pending_status=TransformJob.STATUS_IN_PROGRESS,
completed_status=TransformJob.STATUS_FAILED,
)
@classmethod
def stop_successful(cls, latency_seconds):
return cls(
latency_seconds=latency_seconds,
pending_status=TransformJob.STATUS_STOPPING,
completed_status=TransformJob.STATUS_STOPPED,
)
class EndpointSummary:
"""
Object representing an endpoint entry in the endpoints list returned by
SageMaker's "ListEndpoints" API:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListEndpoints.html.
"""
def __init__(self, endpoint, arn):
self.endpoint = endpoint
self.arn = arn
@property
def response_object(self):
return {
"EndpointName": self.endpoint.endpoint_name,
"CreationTime": self.endpoint.creation_time,
"LastModifiedTime": self.endpoint.last_modified_time,
"EndpointStatus": self.endpoint.status,
"EndpointArn": self.arn,
}
class EndpointDescription:
"""
Object representing an endpoint description returned by SageMaker's
"DescribeEndpoint" API:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeEndpoint.html.
"""
def __init__(self, endpoint, config, arn):
self.endpoint = endpoint
self.config = config
self.arn = arn
@property
def response_object(self):
return {
"EndpointName": self.endpoint.endpoint_name,
"EndpointArn": self.arn,
"EndpointConfigName": self.endpoint.config_name,
"ProductionVariants": self.config.production_variants,
"EndpointStatus": self.endpoint.status,
"CreationTime": self.endpoint.creation_time,
"LastModifiedTime": self.endpoint.last_modified_time,
}
class EndpointConfig(TimestampedResource):
"""
Object representing a SageMaker endpoint configuration. The SageMakerBackend will create
and manage EndpointConfigs.
"""
def __init__(self, config_name, production_variants, tags, async_inference_config=None):
super().__init__()
self.config_name = config_name
self.production_variants = production_variants
self.tags = tags
self.async_inference_config = async_inference_config
@property
def arn_descriptor(self):
return f":endpoint-config/{self.config_name}"
class EndpointConfigSummary:
"""
Object representing an endpoint configuration entry in the configurations list returned by
SageMaker's "ListEndpointConfigs" API:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListEndpointConfigs.html.
"""
def __init__(self, config, arn):
self.config = config
self.arn = arn
@property
def response_object(self):
return {
"EndpointConfigName": self.config.config_name,
"EndpointArn": self.arn,
"CreationTime": self.config.creation_time,
}
class EndpointConfigDescription:
"""
Object representing an endpoint configuration description returned by SageMaker's
"DescribeEndpointConfig" API:
https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeEndpointConfig.html.
"""
def __init__(self, config, arn):
self.config = config
self.arn = arn
@property
def response_object(self):
return {
"EndpointConfigName": self.config.config_name,
"EndpointConfigArn": self.arn,
"ProductionVariants": self.config.production_variants,
"CreationTime": self.config.creation_time,
"AsyncInferenceConfig": self.config.async_inference_config,
}
class Model(TimestampedResource):
"""
Object representing a SageMaker model. The SageMakerBackend will create and manage Models.
"""
def __init__(self, model_name, primary_container, execution_role_arn, tags, vpc_config):
super().__init__()
self.model_name = model_name
self.primary_container = primary_container
self.execution_role_arn = execution_role_arn
self.tags = tags
self.vpc_config = vpc_config
@property
def arn_descriptor(self):
return f":model/{self.model_name}"
class ModelSummary:
"""
Object representing a model entry in the models list returned by SageMaker's
"ListModels" API: https://docs.aws.amazon.com/sagemaker/latest/dg/API_ListModels.html.
"""
def __init__(self, model, arn):
self.model = model
self.arn = arn
@property
def response_object(self):
return {
"ModelArn": self.arn,
"ModelName": self.model.model_name,
"CreationTime": self.model.creation_time,
}
class ModelDescription:
"""
Object representing a model description returned by SageMaker's
"DescribeModel" API: https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeModel.html.
"""
def __init__(self, model, arn):
self.model = model
self.arn = arn
@property
def response_object(self):
return {
"ModelArn": self.arn,
"ModelName": self.model.model_name,
"PrimaryContainer": self.model.primary_container,
"ExecutionRoleArn": self.model.execution_role_arn,
"VpcConfig": self.model.vpc_config or {},
"CreationTime": self.model.creation_time,
}
class TransformJobSummary:
"""
Object representing a TransformJobSummary entry in the TransformJobSummaries list returned by
SageMaker's "ListTransformJobs" API:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ListTransformJobs.html.
"""
def __init__(self, transform_job, arn):
self.transform_job = transform_job
self.arn = arn
@property
def response_object(self):
return {
"TransformJobName": self.transform_job.job_name,
"TransformJobArn": self.arn,
"CreationTime": self.transform_job.creation_time,
"LastModifiedTime": self.transform_job.last_modified_time,
"TransformJobStatus": self.transform_job.status,
}
class TransformJobDescription:
"""
Object representing a transform job description returned by SageMaker's
"DescribeTransformJob" API:
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeTransformJob.html.
"""
def __init__(self, transform_job, arn):
self.transform_job = transform_job
self.arn = arn
@property
def response_object(self):
return {
"TransformJobName": self.transform_job.job_name,
"TransformJobArn": self.arn,
"CreationTime": self.transform_job.creation_time,
"LastModifiedTime": self.transform_job.last_modified_time,
"TransformJobStatus": self.transform_job.status,
"ModelName": self.transform_job.model_name,
}
# Create a SageMaker backend for EC2 region: "us-west-2"
sagemaker_backends = BackendDict(SageMakerBackend, "sagemaker")
mock_sagemaker = base_decorator(sagemaker_backends)