Adds CRUD support for databrew recipe jobs and profile jobs (#5244)

This commit is contained in:
Michael Sanders 2022-06-20 16:38:56 +01:00 committed by GitHub
parent d3cee3c6fa
commit e004c6d218
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 788 additions and 34 deletions

View File

@ -1069,32 +1069,32 @@
## databrew
<details>
<summary>34% implemented</summary>
<summary>54% implemented</summary>
- [ ] batch_delete_recipe_version
- [X] create_dataset
- [ ] create_profile_job
- [X] create_profile_job
- [ ] create_project
- [X] create_recipe
- [ ] create_recipe_job
- [X] create_recipe_job
- [X] create_ruleset
- [ ] create_schedule
- [X] delete_dataset
- [ ] delete_job
- [X] delete_job
- [ ] delete_project
- [X] delete_recipe_version
- [X] delete_ruleset
- [ ] delete_schedule
- [X] describe_dataset
- [ ] describe_job
- [X] describe_job
- [ ] describe_job_run
- [ ] describe_project
- [ ] describe_recipe
- [ ] describe_ruleset
- [X] describe_recipe
- [X] describe_ruleset
- [ ] describe_schedule
- [X] list_datasets
- [ ] list_job_runs
- [ ] list_jobs
- [X] list_jobs
- [ ] list_projects
- [X] list_recipe_versions
- [X] list_recipes
@ -1109,10 +1109,10 @@
- [ ] tag_resource
- [ ] untag_resource
- [X] update_dataset
- [ ] update_profile_job
- [X] update_profile_job
- [ ] update_project
- [X] update_recipe
- [ ] update_recipe_job
- [X] update_recipe_job
- [X] update_ruleset
- [ ] update_schedule
</details>
@ -6163,4 +6163,4 @@
- workspaces
- workspaces-web
- xray
</details>
</details>

View File

@ -27,28 +27,28 @@ databrew
- [ ] batch_delete_recipe_version
- [X] create_dataset
- [ ] create_profile_job
- [X] create_profile_job
- [ ] create_project
- [X] create_recipe
- [ ] create_recipe_job
- [X] create_recipe_job
- [X] create_ruleset
- [ ] create_schedule
- [X] delete_dataset
- [ ] delete_job
- [X] delete_job
- [ ] delete_project
- [X] delete_recipe_version
- [X] delete_ruleset
- [ ] delete_schedule
- [X] describe_dataset
- [ ] describe_job
- [X] describe_job
- [ ] describe_job_run
- [ ] describe_project
- [ ] describe_recipe
- [ ] describe_ruleset
- [X] describe_recipe
- [X] describe_ruleset
- [ ] describe_schedule
- [X] list_datasets
- [ ] list_job_runs
- [ ] list_jobs
- [X] list_jobs
- [ ] list_projects
- [X] list_recipe_versions
- [X] list_recipes
@ -63,10 +63,10 @@ databrew
- [ ] tag_resource
- [ ] untag_resource
- [X] update_dataset
- [ ] update_profile_job
- [X] update_profile_job
- [ ] update_project
- [X] update_recipe
- [ ] update_recipe_job
- [X] update_recipe_job
- [X] update_ruleset
- [ ] update_schedule

View File

@ -1,3 +1,6 @@
from abc import ABCMeta
from abc import abstractmethod
from collections import OrderedDict
from copy import deepcopy
import math
@ -5,6 +8,9 @@ from datetime import datetime
from moto.core import BaseBackend, BaseModel
from moto.core.utils import BackendDict
from moto.core.utils import underscores_to_camelcase
from moto.core.utils import camelcase_to_pascal
from moto.utilities.paginator import paginate
from .exceptions import (
@ -43,6 +49,12 @@ class DataBrewBackend(BaseBackend):
"limit_default": 100,
"unique_attribute": "name",
},
"list_jobs": {
"input_token": "next_token",
"limit_key": "max_results",
"limit_default": 100,
"unique_attribute": "name",
},
}
def __init__(self, region_name, account_id):
@ -50,6 +62,7 @@ class DataBrewBackend(BaseBackend):
self.recipes = OrderedDict()
self.rulesets = OrderedDict()
self.datasets = OrderedDict()
self.jobs = OrderedDict()
@staticmethod
def validate_length(param, param_name, max_length):
@ -145,7 +158,7 @@ class DataBrewBackend(BaseBackend):
]
return [r for r in recipe_versions if r is not None]
def get_recipe(self, recipe_name, recipe_version=None):
def describe_recipe(self, recipe_name, recipe_version=None):
# https://docs.aws.amazon.com/databrew/latest/dg/API_DescribeRecipe.html
self.validate_length(recipe_name, "name", 255)
@ -211,7 +224,7 @@ class DataBrewBackend(BaseBackend):
return ruleset
def get_ruleset(self, ruleset_name):
def describe_ruleset(self, ruleset_name):
if ruleset_name not in self.rulesets:
raise RulesetNotFoundException(ruleset_name)
return self.rulesets[ruleset_name]
@ -295,6 +308,99 @@ class DataBrewBackend(BaseBackend):
return self.datasets[dataset_name]
def describe_job(self, job_name):
# https://docs.aws.amazon.com/databrew/latest/dg/API_DescribeJob.html
self.validate_length(job_name, "name", 240)
if job_name not in self.jobs:
raise ResourceNotFoundException(f"Job {job_name} wasn't found.")
return self.jobs[job_name]
def delete_job(self, job_name):
# https://docs.aws.amazon.com/databrew/latest/dg/API_DeleteJob.html
self.validate_length(job_name, "name", 240)
if job_name not in self.jobs:
raise ResourceNotFoundException(f"The job {job_name} wasn't found.")
del self.jobs[job_name]
def create_profile_job(self, **kwargs):
# https://docs.aws.amazon.com/databrew/latest/dg/API_CreateProfileJob.html
job_name = kwargs["name"]
self.validate_length(job_name, "name", 240)
if job_name in self.jobs:
raise ConflictException(
f"The job {job_name} {self.jobs[job_name].job_type.lower()} job already exists."
)
job = FakeProfileJob(
account_id=self.account_id, region_name=self.region_name, **kwargs
)
self.jobs[job_name] = job
return job
def create_recipe_job(self, **kwargs):
# https://docs.aws.amazon.com/databrew/latest/dg/API_CreateRecipeJob.html
job_name = kwargs["name"]
self.validate_length(job_name, "name", 240)
if job_name in self.jobs:
raise ConflictException(
f"The job {job_name} {self.jobs[job_name].job_type.lower()} job already exists."
)
job = FakeRecipeJob(
account_id=self.account_id, region_name=self.region_name, **kwargs
)
self.jobs[job_name] = job
return job
def update_job(self, **kwargs):
job_name = kwargs["name"]
self.validate_length(job_name, "name", 240)
if job_name not in self.jobs:
raise ResourceNotFoundException(f"The job {job_name} wasn't found")
job = self.jobs[job_name]
for param, value in kwargs.items():
setattr(job, param, value)
return job
def update_recipe_job(self, **kwargs):
# https://docs.aws.amazon.com/databrew/latest/dg/API_UpdateRecipeJob.html
return self.update_job(**kwargs)
def update_profile_job(self, **kwargs):
# https://docs.aws.amazon.com/databrew/latest/dg/API_UpdateProfileJob.html
return self.update_job(**kwargs)
@paginate(pagination_model=PAGINATION_MODEL)
def list_jobs(self, dataset_name=None, project_name=None):
# https://docs.aws.amazon.com/databrew/latest/dg/API_ListJobs.html
if dataset_name is not None:
self.validate_length(dataset_name, "datasetName", 255)
if project_name is not None:
self.validate_length(project_name, "projectName", 255)
def filter_jobs(job):
if dataset_name is not None and job.dataset_name != dataset_name:
return False
if (
project_name is not None
and getattr(job, "project_name", None) != project_name
):
return False
return True
return list(filter(filter_jobs, self.jobs.values()))
class FakeRecipe(BaseModel):
INITIAL_VERSION = 0.1
@ -424,7 +530,7 @@ class FakeRuleset(BaseModel):
"Rules": self.rules,
"Description": self.description,
"TargetArn": self.target_arn,
"CreateTime": self.created_time.isoformat(),
"CreateDate": "%.3f" % self.created_time.timestamp(),
"Tags": self.tags or dict(),
}
@ -464,10 +570,106 @@ class FakeDataset(BaseModel):
"FormatOptions": self.format_options,
"Input": self.input,
"PathOptions": self.path_options,
"CreateTime": self.created_time.isoformat(),
"CreateDate": "%.3f" % self.created_time.timestamp(),
"Tags": self.tags or dict(),
"ResourceArn": self.resource_arn,
}
class BaseModelABCMeta(ABCMeta, type(BaseModel)):
pass
class FakeJob(BaseModel, metaclass=BaseModelABCMeta):
ENCRYPTION_MODES = ("SSE-S3", "SSE-KMS")
LOG_SUBSCRIPTION_VALUES = ("ENABLE", "DISABLE")
@property
@abstractmethod
def local_attrs(self) -> tuple:
raise NotImplementedError
def __init__(self, account_id, region_name, **kwargs):
self.account_id = account_id
self.region_name = region_name
self.name = kwargs.get("name")
self.created_time = datetime.now()
self.dataset_name = kwargs.get("dataset_name")
self.encryption_mode = kwargs.get("encryption_mode")
self.log_subscription = kwargs.get("log_subscription")
self.max_capacity = kwargs.get("max_capacity")
self.max_retries = kwargs.get("max_retries")
self.role_arn = kwargs.get("role_arn")
self.tags = kwargs.get("tags")
self.validate()
# Set attributes specific to subclass
for k in self.local_attrs:
setattr(self, k, kwargs.get(k))
def validate(self):
if self.encryption_mode is not None:
if self.encryption_mode not in FakeJob.ENCRYPTION_MODES:
raise ValidationException(
f"1 validation error detected: Value '{self.encryption_mode}' at 'encryptionMode' failed to satisfy constraint: Member must satisfy enum value set: [{', '.join(self.ENCRYPTION_MODES)}]"
)
if self.log_subscription is not None:
if self.log_subscription not in FakeJob.LOG_SUBSCRIPTION_VALUES:
raise ValidationException(
f"1 validation error detected: Value '{self.log_subscription}' at 'logSubscription' failed to satisfy constraint: Member must satisfy enum value set: [{', '.join(self.LOG_SUBSCRIPTION_VALUES)}]"
)
@property
@abstractmethod
def job_type(self) -> str:
pass
@property
def resource_arn(self):
return f"arn:aws:databrew:{self.region_name}:{self.account_id}:job/{self.name}"
def as_dict(self):
rtn_dict = {
"Name": self.name,
"AccountId": self.account_id,
"CreateDate": "%.3f" % self.created_time.timestamp(),
"DatasetName": self.dataset_name,
"EncryptionMode": self.encryption_mode,
"Tags": self.tags or dict(),
"LogSubscription": self.log_subscription,
"MaxCapacity": self.max_capacity,
"MaxRetries": self.max_retries,
"ResourceArn": self.resource_arn,
"RoleArn": self.role_arn,
"Type": self.job_type,
}
# Add in subclass attributes
for k in self.local_attrs:
rtn_dict[camelcase_to_pascal(underscores_to_camelcase(k))] = getattr(
self, k
)
# Remove items that have a value of None
rtn_dict = {k: v for k, v in rtn_dict.items() if v is not None}
return rtn_dict
class FakeProfileJob(FakeJob):
job_type = "PROFILE"
local_attrs = ("output_location", "configuration", "validation_configurations")
class FakeRecipeJob(FakeJob):
local_attrs = (
"database_outputs",
"data_catalog_outputs",
"outputs",
"project_name",
"recipe_reference",
)
job_type = "RECIPE"
databrew_backends = BackendDict(DataBrewBackend, "databrew")

View File

@ -117,7 +117,7 @@ class DataBrewResponse(BaseResponse):
recipe_version = self._get_param(
"RecipeVersion", self._get_param("recipeVersion")
)
recipe = self.databrew_backend.get_recipe(
recipe = self.databrew_backend.describe_recipe(
recipe_name, recipe_version=recipe_version
)
return 200, {}, json.dumps(recipe.as_dict())
@ -167,12 +167,12 @@ class DataBrewResponse(BaseResponse):
return 200, {}, json.dumps(ruleset.as_dict())
def get_ruleset_response(self, ruleset_name):
ruleset = self.databrew_backend.get_ruleset(ruleset_name)
return 201, {}, json.dumps(ruleset.as_dict())
ruleset = self.databrew_backend.describe_ruleset(ruleset_name)
return 200, {}, json.dumps(ruleset.as_dict())
def delete_ruleset_response(self, ruleset_name):
self.databrew_backend.delete_ruleset(ruleset_name)
return 204, {}, ""
return 200, {}, json.dumps({"Name": ruleset_name})
@amzn_request_id
def ruleset_response(self, request, full_url, headers):
@ -298,3 +298,150 @@ class DataBrewResponse(BaseResponse):
return self.update_dataset(dataset_name)
# endregion
# region Jobs
@amzn_request_id
def list_jobs(self, request, full_url, headers):
# https://docs.aws.amazon.com/databrew/latest/dg/API_ListJobs.html
self.setup_class(request, full_url, headers)
dataset_name = self._get_param("datasetName")
project_name = self._get_param("projectName")
next_token = self._get_param("NextToken", self._get_param("nextToken"))
max_results = self._get_int_param(
"MaxResults", self._get_int_param("maxResults")
)
# pylint: disable=unexpected-keyword-arg, unbalanced-tuple-unpacking
job_list, next_token = self.databrew_backend.list_jobs(
dataset_name=dataset_name,
project_name=project_name,
next_token=next_token,
max_results=max_results,
)
return json.dumps(
{
"Jobs": [job.as_dict() for job in job_list],
"NextToken": next_token,
}
)
def get_job_response(self, job_name):
job = self.databrew_backend.describe_job(job_name)
return 200, {}, json.dumps(job.as_dict())
def delete_job_response(self, job_name):
self.databrew_backend.delete_job(job_name)
return 200, {}, json.dumps({"Name": job_name})
@amzn_request_id
def job_response(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
parsed_url = urlparse(full_url)
job_name = parsed_url.path.rstrip("/").rsplit("/", 1)[1]
if request.method == "GET":
return self.get_job_response(job_name)
elif request.method == "DELETE":
return self.delete_job_response(job_name)
@amzn_request_id
def create_profile_job(self):
# https://docs.aws.amazon.com/databrew/latest/dg/API_CreateProfileJob.html
kwargs = {
"dataset_name": self._get_param("DatasetName"),
"name": self._get_param("Name"),
"output_location": self._get_param("OutputLocation"),
"role_arn": self._get_param("RoleArn"),
"configuration": self._get_param("Configuration"),
"encryption_key_arn": self._get_param("EncryptionKeyArn"),
"encryption_mode": self._get_param("EncryptionMode"),
"job_sample": self._get_param("JobSample"),
"log_subscription": self._get_param("LogSubscription"),
"max_capacity": self._get_int_param("MaxCapacity"),
"max_retries": self._get_int_param("MaxRetries"),
"tags": self._get_param("Tags"),
"timeout": self._get_int_param("Timeout"),
"validation_configurations": self._get_param("ValidationConfigurations"),
}
return json.dumps(self.databrew_backend.create_profile_job(**kwargs).as_dict())
def update_profile_job_response(self, name):
# https://docs.aws.amazon.com/databrew/latest/dg/API_UpdateProfileJob.html
kwargs = {
"name": name,
"output_location": self._get_param("OutputLocation"),
"role_arn": self._get_param("RoleArn"),
"configuration": self._get_param("Configuration"),
"encryption_key_arn": self._get_param("EncryptionKeyArn"),
"encryption_mode": self._get_param("EncryptionMode"),
"job_sample": self._get_param("JobSample"),
"log_subscription": self._get_param("LogSubscription"),
"max_capacity": self._get_int_param("MaxCapacity"),
"max_retries": self._get_int_param("MaxRetries"),
"timeout": self._get_int_param("Timeout"),
"validation_configurations": self._get_param("ValidationConfigurations"),
}
return json.dumps(self.databrew_backend.update_profile_job(**kwargs).as_dict())
@amzn_request_id
def create_recipe_job(self):
# https://docs.aws.amazon.com/databrew/latest/dg/API_CreateRecipeJob.html
kwargs = {
"name": self._get_param("Name"),
"role_arn": self._get_param("RoleArn"),
"database_outputs": self._get_param("DatabaseOutputs"),
"data_catalog_outputs": self._get_param("DataCatalogOutputs"),
"dataset_name": self._get_param("DatasetName"),
"encryption_key_arn": self._get_param("EncryptionKeyArn"),
"encryption_mode": self._get_param("EncryptionMode"),
"log_subscription": self._get_param("LogSubscription"),
"max_capacity": self._get_int_param("MaxCapacity"),
"max_retries": self._get_int_param("MaxRetries"),
"outputs": self._get_param("Outputs"),
"project_name": self._get_param("ProjectName"),
"recipe_reference": self._get_param("RecipeReference"),
"tags": self._get_param("Tags"),
"timeout": self._get_int_param("Timeout"),
}
return json.dumps(self.databrew_backend.create_recipe_job(**kwargs).as_dict())
@amzn_request_id
def update_recipe_job_response(self, name):
# https://docs.aws.amazon.com/databrew/latest/dg/API_UpdateRecipeJob.html
kwargs = {
"name": name,
"role_arn": self._get_param("RoleArn"),
"database_outputs": self._get_param("DatabaseOutputs"),
"data_catalog_outputs": self._get_param("DataCatalogOutputs"),
"encryption_key_arn": self._get_param("EncryptionKeyArn"),
"encryption_mode": self._get_param("EncryptionMode"),
"log_subscription": self._get_param("LogSubscription"),
"max_capacity": self._get_int_param("MaxCapacity"),
"max_retries": self._get_int_param("MaxRetries"),
"outputs": self._get_param("Outputs"),
"timeout": self._get_int_param("Timeout"),
}
return json.dumps(self.databrew_backend.update_recipe_job(**kwargs).as_dict())
@amzn_request_id
def profile_job_response(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
parsed_url = urlparse(full_url)
job_name = parsed_url.path.rstrip("/").rsplit("/", 1)[1]
if request.method == "PUT":
return self.update_profile_job_response(job_name)
@amzn_request_id
def recipe_job_response(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
parsed_url = urlparse(full_url)
job_name = parsed_url.path.rstrip("/").rsplit("/", 1)[1]
if request.method == "PUT":
return self.update_recipe_job_response(job_name)
# endregion

View File

@ -12,4 +12,10 @@ url_paths = {
"{0}/rulesets/(?P<ruleset_name>[^/]+)$": DataBrewResponse().ruleset_response,
"{0}/datasets$": DataBrewResponse.dispatch,
"{0}/datasets/(?P<dataset_name>[^/]+)$": DataBrewResponse().dataset_response,
"{0}/jobs$": DataBrewResponse().list_jobs,
"{0}/jobs/(?P<job_name>[^/]+)$": DataBrewResponse().job_response,
"{0}/profileJobs$": DataBrewResponse.dispatch,
"{0}/recipeJobs$": DataBrewResponse.dispatch,
"{0}/profileJobs/(?P<job_name>[^/]+)$": DataBrewResponse().profile_job_response,
"{0}/recipeJobs/(?P<job_name>[^/]+)$": DataBrewResponse().recipe_job_response,
}

View File

@ -0,0 +1,395 @@
import uuid
import boto3
import pytest
from botocore.exceptions import ClientError
from moto import mock_databrew
from moto.core import ACCOUNT_ID
def _create_databrew_client():
client = boto3.client("databrew", region_name="us-west-1")
return client
def _create_test_profile_job(
client,
dataset_name=None,
job_name=None,
output_location=None,
role_arn=None,
tags=None,
):
kwargs = {}
kwargs["Name"] = job_name or str(uuid.uuid4())
kwargs["RoleArn"] = role_arn or str(uuid.uuid4())
kwargs["DatasetName"] = dataset_name or str(uuid.uuid4())
kwargs["OutputLocation"] = output_location or {"Bucket": str(uuid.uuid4())}
if tags is not None:
kwargs["Tags"] = tags
return client.create_profile_job(**kwargs)
def _create_test_recipe_job(
client,
job_name=None,
role_arn=None,
tags=None,
encryption_mode=None,
log_subscription=None,
dataset_name=None,
project_name=None,
):
kwargs = {}
kwargs["Name"] = job_name or str(uuid.uuid4())
kwargs["RoleArn"] = role_arn or str(uuid.uuid4())
if tags is not None:
kwargs["Tags"] = tags
if encryption_mode is not None:
kwargs["EncryptionMode"] = encryption_mode
if log_subscription is not None:
kwargs["LogSubscription"] = log_subscription
if dataset_name is not None:
kwargs["DatasetName"] = dataset_name
if project_name is not None:
kwargs["ProjectName"] = project_name
return client.create_recipe_job(**kwargs)
def _create_test_recipe_jobs(client, count, **kwargs):
for _ in range(count):
_create_test_recipe_job(client, **kwargs)
def _create_test_profile_jobs(client, count, **kwargs):
for _ in range(count):
_create_test_profile_job(client, **kwargs)
@mock_databrew
def test_create_profile_job_that_already_exists():
client = _create_databrew_client()
response = _create_test_profile_job(client)
job_name = response["Name"]
with pytest.raises(ClientError) as exc:
_create_test_profile_job(client, job_name=response["Name"])
err = exc.value.response["Error"]
err["Code"].should.equal("ConflictException")
err["Message"].should.equal(f"The job {job_name} profile job already exists.")
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
@mock_databrew
def test_create_recipe_job_that_already_exists():
client = _create_databrew_client()
response = _create_test_recipe_job(client)
job_name = response["Name"]
with pytest.raises(ClientError) as exc:
_create_test_recipe_job(client, job_name=response["Name"])
err = exc.value.response["Error"]
err["Code"].should.equal("ConflictException")
err["Message"].should.equal(f"The job {job_name} recipe job already exists.")
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
@mock_databrew
def test_create_recipe_job_with_invalid_encryption_mode():
client = _create_databrew_client()
with pytest.raises(ClientError) as exc:
_create_test_recipe_job(client, encryption_mode="INVALID")
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"1 validation error detected: Value 'INVALID' at 'encryptionMode' failed to satisfy constraint: "
"Member must satisfy enum value set: [SSE-S3, SSE-KMS]"
)
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
@mock_databrew
def test_create_recipe_job_with_invalid_log_subscription_value():
client = _create_databrew_client()
with pytest.raises(ClientError) as exc:
_create_test_recipe_job(client, log_subscription="INVALID")
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"1 validation error detected: Value 'INVALID' at 'logSubscription' failed to satisfy constraint: "
"Member must satisfy enum value set: [ENABLE, DISABLE]"
)
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
@mock_databrew
def test_create_recipe_job_with_same_name_as_profile_job():
client = _create_databrew_client()
response = _create_test_profile_job(client)
job_name = response["Name"]
with pytest.raises(ClientError) as exc:
_create_test_recipe_job(client, job_name=response["Name"])
err = exc.value.response["Error"]
err["Code"].should.equal("ConflictException")
err["Message"].should.equal(f"The job {job_name} profile job already exists.")
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
@mock_databrew
def test_describe_recipe_job():
client = _create_databrew_client()
response = _create_test_recipe_job(client)
job_name = response["Name"]
job = client.describe_job(Name=job_name)
job.should.have.key("Name").equal(response["Name"])
job.should.have.key("Type").equal("RECIPE")
job.should.have.key("ResourceArn").equal(
f"arn:aws:databrew:us-west-1:{ACCOUNT_ID}:job/{job_name}"
)
job["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_describe_job_that_does_not_exist():
client = _create_databrew_client()
with pytest.raises(ClientError) as exc:
client.describe_job(Name="DoesNotExist")
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
err["Message"].should.equal("Job DoesNotExist wasn't found.")
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
@mock_databrew
def test_describe_job_with_long_name():
client = _create_databrew_client()
name = "a" * 241
with pytest.raises(ClientError) as exc:
client.describe_job(Name=name)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: "
f"Member must have length less than or equal to 240"
)
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
@mock_databrew
def test_update_profile_job():
client = _create_databrew_client()
# Create the job
response = _create_test_profile_job(client)
job_name = response["Name"]
# Update the job by changing RoleArn
update_response = client.update_profile_job(
Name=job_name, RoleArn="a" * 20, OutputLocation={"Bucket": "b" * 20}
)
update_response.should.have.key("Name").equals(job_name)
update_response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
# Describe the job to check that RoleArn was updated
job = client.describe_job(Name=job_name)
job.should.have.key("Name").equal(response["Name"])
job.should.have.key("RoleArn").equal("a" * 20)
@mock_databrew
def test_update_recipe_job():
client = _create_databrew_client()
# Create the job
response = _create_test_recipe_job(client)
job_name = response["Name"]
# Update the job by changing RoleArn
update_response = client.update_recipe_job(Name=job_name, RoleArn="a" * 20)
update_response.should.have.key("Name").equals(job_name)
update_response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
# Describe the job to check that RoleArn was updated
job = client.describe_job(Name=job_name)
job.should.have.key("Name").equal(response["Name"])
job.should.have.key("RoleArn").equal("a" * 20)
@mock_databrew
def test_update_profile_job_does_not_exist():
client = _create_databrew_client()
with pytest.raises(ClientError) as exc:
client.update_profile_job(
Name="DoesNotExist", RoleArn="a" * 20, OutputLocation={"Bucket": "b" * 20}
)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
err["Message"].should.equal("The job DoesNotExist wasn't found")
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
@mock_databrew
def test_update_recipe_job_does_not_exist():
client = _create_databrew_client()
with pytest.raises(ClientError) as exc:
client.update_recipe_job(Name="DoesNotExist", RoleArn="a" * 20)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
err["Message"].should.equal("The job DoesNotExist wasn't found")
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
@mock_databrew
def test_delete_job():
client = _create_databrew_client()
# Create the job
response = _create_test_recipe_job(client)
job_name = response["Name"]
# Delete the job
response = client.delete_job(Name=job_name)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
response["Name"].should.equal(job_name)
# Check the job does not exist anymore
with pytest.raises(ClientError) as exc:
client.describe_job(Name=job_name)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
err["Message"].should.equal(f"Job {job_name} wasn't found.")
@mock_databrew
def test_delete_job_does_not_exist():
client = _create_databrew_client()
# Delete the job
with pytest.raises(ClientError) as exc:
client.delete_job(Name="DoesNotExist")
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
err["Message"].should.equal("The job DoesNotExist wasn't found.")
@mock_databrew
def test_delete_job_with_long_name():
client = _create_databrew_client()
name = "a" * 241
with pytest.raises(ClientError) as exc:
client.delete_job(Name=name)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: "
f"Member must have length less than or equal to 240"
)
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
@mock_databrew
def test_job_list_when_empty():
client = _create_databrew_client()
response = client.list_jobs()
response.should.have.key("Jobs")
response["Jobs"].should.have.length_of(0)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_list_jobs_with_max_results():
client = _create_databrew_client()
_create_test_recipe_jobs(client, 4)
response = client.list_jobs(MaxResults=2)
response["Jobs"].should.have.length_of(2)
response.should.have.key("NextToken")
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_list_jobs_from_next_token():
client = _create_databrew_client()
_create_test_recipe_jobs(client, 10)
first_response = client.list_jobs(MaxResults=3)
response = client.list_jobs(NextToken=first_response["NextToken"])
response["Jobs"].should.have.length_of(7)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_list_jobs_with_max_results_greater_than_actual_results():
client = _create_databrew_client()
_create_test_recipe_jobs(client, 4)
response = client.list_jobs(MaxResults=10)
response["Jobs"].should.have.length_of(4)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_list_jobs_recipe_and_profile():
client = _create_databrew_client()
_create_test_recipe_jobs(client, 4)
_create_test_profile_jobs(client, 2)
response = client.list_jobs()
response["Jobs"].should.have.length_of(6)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_list_jobs_dataset_name_filter():
client = _create_databrew_client()
_create_test_recipe_jobs(client, 3, dataset_name="TEST")
_create_test_recipe_jobs(client, 1)
_create_test_profile_jobs(client, 4, dataset_name="TEST")
_create_test_profile_jobs(client, 1)
response = client.list_jobs(DatasetName="TEST")
response["Jobs"].should.have.length_of(7)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_list_jobs_project_name_filter():
client = _create_databrew_client()
_create_test_recipe_jobs(client, 3, project_name="TEST_PROJECT")
_create_test_recipe_jobs(client, 1)
_create_test_profile_jobs(client, 1)
response = client.list_jobs(ProjectName="TEST_PROJECT")
response["Jobs"].should.have.length_of(3)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
def test_list_jobs_dataset_name_and_project_name_filter():
client = _create_databrew_client()
_create_test_recipe_jobs(client, 1, dataset_name="TEST")
_create_test_recipe_jobs(client, 1, project_name="TEST_PROJECT")
_create_test_recipe_jobs(
client, 10, dataset_name="TEST", project_name="TEST_PROJECT"
)
_create_test_recipe_jobs(client, 1)
_create_test_profile_jobs(client, 1)
response = client.list_jobs(DatasetName="TEST", ProjectName="TEST_PROJECT")
response["Jobs"].should.have.length_of(10)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)

View File

@ -86,6 +86,7 @@ def test_describe_ruleset():
ruleset["Name"].should.equal(response["Name"])
ruleset["Rules"].should.have.length_of(1)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_databrew
@ -116,28 +117,31 @@ def test_create_ruleset_that_already_exists():
def test_delete_ruleset():
client = _create_databrew_client()
response = _create_test_ruleset(client)
ruleset_name = response["Name"]
# Check ruleset exists
ruleset = client.describe_ruleset(Name=response["Name"])
ruleset = client.describe_ruleset(Name=ruleset_name)
ruleset["Name"].should.equal(response["Name"])
# Delete the ruleset
client.delete_ruleset(Name=response["Name"])
response = client.delete_ruleset(Name=ruleset_name)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
response["Name"].should.equal(ruleset_name)
# Check it does not exist anymore
with pytest.raises(ClientError) as exc:
client.describe_ruleset(Name=response["Name"])
client.describe_ruleset(Name=ruleset_name)
err = exc.value.response["Error"]
err["Code"].should.equal("EntityNotFoundException")
err["Message"].should.equal(f"Ruleset {response['Name']} not found.")
err["Message"].should.equal(f"Ruleset {ruleset_name} not found.")
# Check that a ruleset that does not exist errors
with pytest.raises(ClientError) as exc:
client.delete_ruleset(Name=response["Name"])
client.delete_ruleset(Name=ruleset_name)
err = exc.value.response["Error"]
err["Code"].should.equal("EntityNotFoundException")
err["Message"].should.equal(f"Ruleset {response['Name']} not found.")
err["Message"].should.equal(f"Ruleset {ruleset_name} not found.")
@mock_databrew