Databrew: add recipe version support (#5094)
This commit is contained in:
parent
a666d59b58
commit
3f89b98889
@ -1069,7 +1069,7 @@
|
|||||||
|
|
||||||
## databrew
|
## databrew
|
||||||
<details>
|
<details>
|
||||||
<summary>15% implemented</summary>
|
<summary>22% implemented</summary>
|
||||||
|
|
||||||
- [ ] batch_delete_recipe_version
|
- [ ] batch_delete_recipe_version
|
||||||
- [ ] create_dataset
|
- [ ] create_dataset
|
||||||
@ -1082,26 +1082,26 @@
|
|||||||
- [ ] delete_dataset
|
- [ ] delete_dataset
|
||||||
- [ ] delete_job
|
- [ ] delete_job
|
||||||
- [ ] delete_project
|
- [ ] delete_project
|
||||||
- [ ] delete_recipe_version
|
- [X] delete_recipe_version
|
||||||
- [X] delete_ruleset
|
- [X] delete_ruleset
|
||||||
- [ ] delete_schedule
|
- [ ] delete_schedule
|
||||||
- [ ] describe_dataset
|
- [ ] describe_dataset
|
||||||
- [ ] describe_job
|
- [ ] describe_job
|
||||||
- [ ] describe_job_run
|
- [ ] describe_job_run
|
||||||
- [ ] describe_project
|
- [ ] describe_project
|
||||||
- [ ] describe_recipe
|
- [X] describe_recipe
|
||||||
- [ ] describe_ruleset
|
- [ ] describe_ruleset
|
||||||
- [ ] describe_schedule
|
- [ ] describe_schedule
|
||||||
- [ ] list_datasets
|
- [ ] list_datasets
|
||||||
- [ ] list_job_runs
|
- [ ] list_job_runs
|
||||||
- [ ] list_jobs
|
- [ ] list_jobs
|
||||||
- [ ] list_projects
|
- [ ] list_projects
|
||||||
- [ ] list_recipe_versions
|
- [X] list_recipe_versions
|
||||||
- [X] list_recipes
|
- [X] list_recipes
|
||||||
- [X] list_rulesets
|
- [X] list_rulesets
|
||||||
- [ ] list_schedules
|
- [ ] list_schedules
|
||||||
- [ ] list_tags_for_resource
|
- [ ] list_tags_for_resource
|
||||||
- [ ] publish_recipe
|
- [X] publish_recipe
|
||||||
- [ ] send_project_session_action
|
- [ ] send_project_session_action
|
||||||
- [ ] start_job_run
|
- [ ] start_job_run
|
||||||
- [ ] start_project_session
|
- [ ] start_project_session
|
||||||
|
@ -36,26 +36,26 @@ databrew
|
|||||||
- [ ] delete_dataset
|
- [ ] delete_dataset
|
||||||
- [ ] delete_job
|
- [ ] delete_job
|
||||||
- [ ] delete_project
|
- [ ] delete_project
|
||||||
- [ ] delete_recipe_version
|
- [X] delete_recipe_version
|
||||||
- [X] delete_ruleset
|
- [X] delete_ruleset
|
||||||
- [ ] delete_schedule
|
- [ ] delete_schedule
|
||||||
- [ ] describe_dataset
|
- [ ] describe_dataset
|
||||||
- [ ] describe_job
|
- [ ] describe_job
|
||||||
- [ ] describe_job_run
|
- [ ] describe_job_run
|
||||||
- [ ] describe_project
|
- [ ] describe_project
|
||||||
- [ ] describe_recipe
|
- [X] describe_recipe
|
||||||
- [ ] describe_ruleset
|
- [ ] describe_ruleset
|
||||||
- [ ] describe_schedule
|
- [ ] describe_schedule
|
||||||
- [ ] list_datasets
|
- [ ] list_datasets
|
||||||
- [ ] list_job_runs
|
- [ ] list_job_runs
|
||||||
- [ ] list_jobs
|
- [ ] list_jobs
|
||||||
- [ ] list_projects
|
- [ ] list_projects
|
||||||
- [ ] list_recipe_versions
|
- [X] list_recipe_versions
|
||||||
- [X] list_recipes
|
- [X] list_recipes
|
||||||
- [X] list_rulesets
|
- [X] list_rulesets
|
||||||
- [ ] list_schedules
|
- [ ] list_schedules
|
||||||
- [ ] list_tags_for_resource
|
- [ ] list_tags_for_resource
|
||||||
- [ ] publish_recipe
|
- [X] publish_recipe
|
||||||
- [ ] send_project_session_action
|
- [ ] send_project_session_action
|
||||||
- [ ] start_job_run
|
- [ ] start_job_run
|
||||||
- [ ] start_project_session
|
- [ ] start_project_session
|
||||||
|
@ -10,9 +10,16 @@ class AlreadyExistsException(DataBrewClientError):
|
|||||||
super().__init__("AlreadyExistsException", "%s already exists." % (typ))
|
super().__init__("AlreadyExistsException", "%s already exists." % (typ))
|
||||||
|
|
||||||
|
|
||||||
class RecipeAlreadyExistsException(AlreadyExistsException):
|
class ConflictException(DataBrewClientError):
|
||||||
def __init__(self):
|
code = 409
|
||||||
super().__init__("Recipe")
|
|
||||||
|
def __init__(self, message, **kwargs):
|
||||||
|
super().__init__("ConflictException", message, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationException(DataBrewClientError):
|
||||||
|
def __init__(self, message, **kwargs):
|
||||||
|
super().__init__("ValidationException", message, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RulesetAlreadyExistsException(AlreadyExistsException):
|
class RulesetAlreadyExistsException(AlreadyExistsException):
|
||||||
@ -25,9 +32,11 @@ class EntityNotFoundException(DataBrewClientError):
|
|||||||
super().__init__("EntityNotFoundException", msg)
|
super().__init__("EntityNotFoundException", msg)
|
||||||
|
|
||||||
|
|
||||||
class RecipeNotFoundException(EntityNotFoundException):
|
class ResourceNotFoundException(DataBrewClientError):
|
||||||
def __init__(self, recipe_name):
|
code = 404
|
||||||
super().__init__("Recipe %s not found." % recipe_name)
|
|
||||||
|
def __init__(self, message, **kwargs):
|
||||||
|
super().__init__("ResourceNotFoundException", message, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RulesetNotFoundException(EntityNotFoundException):
|
class RulesetNotFoundException(EntityNotFoundException):
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from copy import deepcopy
|
||||||
|
import math
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.utils import BackendDict
|
from moto.core.utils import BackendDict
|
||||||
from moto.utilities.paginator import paginate
|
from moto.utilities.paginator import paginate
|
||||||
from .exceptions import RecipeAlreadyExistsException, RecipeNotFoundException
|
from .exceptions import (
|
||||||
|
ConflictException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
from .exceptions import RulesetAlreadyExistsException, RulesetNotFoundException
|
from .exceptions import RulesetAlreadyExistsException, RulesetNotFoundException
|
||||||
|
|
||||||
|
|
||||||
@ -16,6 +22,12 @@ class DataBrewBackend(BaseBackend):
|
|||||||
"limit_default": 100,
|
"limit_default": 100,
|
||||||
"unique_attribute": "name",
|
"unique_attribute": "name",
|
||||||
},
|
},
|
||||||
|
"list_recipe_versions": {
|
||||||
|
"input_token": "next_token",
|
||||||
|
"limit_key": "max_results",
|
||||||
|
"limit_default": 100,
|
||||||
|
"unique_attribute": "name",
|
||||||
|
},
|
||||||
"list_rulesets": {
|
"list_rulesets": {
|
||||||
"input_token": "next_token",
|
"input_token": "next_token",
|
||||||
"limit_key": "max_results",
|
"limit_key": "max_results",
|
||||||
@ -34,42 +46,134 @@ class DataBrewBackend(BaseBackend):
|
|||||||
region_name = self.region_name
|
region_name = self.region_name
|
||||||
self.__init__(region_name)
|
self.__init__(region_name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_length(param, param_name, max_length):
|
||||||
|
if len(param) > max_length:
|
||||||
|
raise ValidationException(
|
||||||
|
f"1 validation error detected: Value '{param}' at '{param_name}' failed to "
|
||||||
|
f"satisfy constraint: Member must have length less than or equal to {max_length}"
|
||||||
|
)
|
||||||
|
|
||||||
def create_recipe(self, recipe_name, recipe_description, recipe_steps, tags):
|
def create_recipe(self, recipe_name, recipe_description, recipe_steps, tags):
|
||||||
# https://docs.aws.amazon.com/databrew/latest/dg/API_CreateRecipe.html
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_CreateRecipe.html
|
||||||
if recipe_name in self.recipes:
|
if recipe_name in self.recipes:
|
||||||
raise RecipeAlreadyExistsException()
|
raise ConflictException(f"The recipe {recipe_name} already exists")
|
||||||
|
|
||||||
recipe = FakeRecipe(
|
recipe = FakeRecipe(
|
||||||
self.region_name, recipe_name, recipe_description, recipe_steps, tags
|
self.region_name, recipe_name, recipe_description, recipe_steps, tags
|
||||||
)
|
)
|
||||||
self.recipes[recipe_name] = recipe
|
self.recipes[recipe_name] = recipe
|
||||||
return recipe
|
return recipe.latest_working
|
||||||
|
|
||||||
def update_recipe(self, recipe_name, recipe_description, recipe_steps, tags):
|
def delete_recipe_version(self, recipe_name, recipe_version):
|
||||||
|
if not FakeRecipe.version_is_valid(recipe_version, latest_published=False):
|
||||||
|
raise ValidationException(
|
||||||
|
f"Recipe {recipe_name} version {recipe_version} is invalid."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
recipe = self.recipes[recipe_name]
|
||||||
|
except KeyError:
|
||||||
|
raise ResourceNotFoundException(f"The recipe {recipe_name} wasn't found")
|
||||||
|
|
||||||
|
if (
|
||||||
|
recipe_version != FakeRecipe.LATEST_WORKING
|
||||||
|
and float(recipe_version) not in recipe.versions
|
||||||
|
):
|
||||||
|
raise ResourceNotFoundException(
|
||||||
|
f"The recipe {recipe_name} version {recipe_version } wasn't found."
|
||||||
|
)
|
||||||
|
|
||||||
|
if recipe_version in (
|
||||||
|
FakeRecipe.LATEST_WORKING,
|
||||||
|
str(recipe.latest_working.version),
|
||||||
|
):
|
||||||
|
if recipe.latest_published is not None:
|
||||||
|
# Can only delete latest working version when there are no others
|
||||||
|
raise ValidationException(
|
||||||
|
f"Recipe version {recipe_version} is not allowed to be deleted"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
del self.recipes[recipe_name]
|
||||||
|
else:
|
||||||
|
recipe.delete_published_version(recipe_version)
|
||||||
|
|
||||||
|
def update_recipe(self, recipe_name, recipe_description, recipe_steps):
|
||||||
if recipe_name not in self.recipes:
|
if recipe_name not in self.recipes:
|
||||||
raise RecipeNotFoundException(recipe_name)
|
raise ResourceNotFoundException(f"The recipe {recipe_name} wasn't found")
|
||||||
|
|
||||||
recipe = self.recipes[recipe_name]
|
recipe = self.recipes[recipe_name]
|
||||||
if recipe_description is not None:
|
recipe.update(recipe_description, recipe_steps)
|
||||||
recipe.description = recipe_description
|
|
||||||
if recipe_steps is not None:
|
|
||||||
recipe.steps = recipe_steps
|
|
||||||
if tags is not None:
|
|
||||||
recipe.tags = tags
|
|
||||||
|
|
||||||
return recipe
|
return recipe.latest_working
|
||||||
|
|
||||||
@paginate(pagination_model=PAGINATION_MODEL)
|
@paginate(pagination_model=PAGINATION_MODEL)
|
||||||
def list_recipes(self):
|
def list_recipes(self, recipe_version=None):
|
||||||
return [self.recipes[key] for key in self.recipes] if self.recipes else []
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_ListRecipes.html
|
||||||
|
if recipe_version == FakeRecipe.LATEST_WORKING:
|
||||||
|
version = "latest_working"
|
||||||
|
elif recipe_version in (None, FakeRecipe.LATEST_PUBLISHED):
|
||||||
|
version = "latest_published"
|
||||||
|
else:
|
||||||
|
raise ValidationException(
|
||||||
|
f"Invalid version {recipe_version}. "
|
||||||
|
"Valid versions are LATEST_PUBLISHED and LATEST_WORKING."
|
||||||
|
)
|
||||||
|
recipes = [getattr(self.recipes[key], version) for key in self.recipes]
|
||||||
|
return [r for r in recipes if r is not None]
|
||||||
|
|
||||||
def get_recipe(self, recipe_name):
|
@paginate(pagination_model=PAGINATION_MODEL)
|
||||||
"""
|
def list_recipe_versions(self, recipe_name):
|
||||||
The Version-parameter has not yet been implemented
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_ListRecipeVersions.html
|
||||||
"""
|
self.validate_length(recipe_name, "name", 255)
|
||||||
if recipe_name not in self.recipes:
|
|
||||||
raise RecipeNotFoundException(recipe_name)
|
recipe = self.recipes.get(recipe_name)
|
||||||
return self.recipes[recipe_name]
|
if recipe is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
latest_working = recipe.latest_working
|
||||||
|
|
||||||
|
recipe_versions = [
|
||||||
|
recipe_version
|
||||||
|
for recipe_version in recipe.versions.values()
|
||||||
|
if recipe_version is not latest_working
|
||||||
|
]
|
||||||
|
return [r for r in recipe_versions if r is not None]
|
||||||
|
|
||||||
|
def get_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)
|
||||||
|
|
||||||
|
if recipe_version is None:
|
||||||
|
recipe_version = FakeRecipe.LATEST_PUBLISHED
|
||||||
|
else:
|
||||||
|
self.validate_length(recipe_version, "recipeVersion", 16)
|
||||||
|
if not FakeRecipe.version_is_valid(recipe_version):
|
||||||
|
raise ValidationException(
|
||||||
|
f"Recipe {recipe_name} version {recipe_version} isn't valid."
|
||||||
|
)
|
||||||
|
|
||||||
|
recipe = None
|
||||||
|
if recipe_name in self.recipes:
|
||||||
|
if recipe_version == FakeRecipe.LATEST_PUBLISHED:
|
||||||
|
recipe = self.recipes[recipe_name].latest_published
|
||||||
|
elif recipe_version == FakeRecipe.LATEST_WORKING:
|
||||||
|
recipe = self.recipes[recipe_name].latest_working
|
||||||
|
else:
|
||||||
|
recipe = self.recipes[recipe_name].versions.get(float(recipe_version))
|
||||||
|
if recipe is None:
|
||||||
|
raise ResourceNotFoundException(
|
||||||
|
f"The recipe {recipe_name} for version {recipe_version} wasn't found."
|
||||||
|
)
|
||||||
|
return recipe
|
||||||
|
|
||||||
|
def publish_recipe(self, recipe_name, description=None):
|
||||||
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_PublishRecipe.html
|
||||||
|
self.validate_length(recipe_name, "name", 255)
|
||||||
|
try:
|
||||||
|
self.recipes[recipe_name].publish(description)
|
||||||
|
except KeyError:
|
||||||
|
raise ResourceNotFoundException(f"Recipe {recipe_name} wasn't found")
|
||||||
|
|
||||||
def create_ruleset(
|
def create_ruleset(
|
||||||
self, ruleset_name, ruleset_description, ruleset_rules, ruleset_target_arn, tags
|
self, ruleset_name, ruleset_description, ruleset_rules, ruleset_target_arn, tags
|
||||||
@ -119,8 +223,77 @@ class DataBrewBackend(BaseBackend):
|
|||||||
|
|
||||||
|
|
||||||
class FakeRecipe(BaseModel):
|
class FakeRecipe(BaseModel):
|
||||||
|
INITIAL_VERSION = 0.1
|
||||||
|
LATEST_WORKING = "LATEST_WORKING"
|
||||||
|
LATEST_PUBLISHED = "LATEST_PUBLISHED"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def version_is_valid(cls, version, latest_working=True, latest_published=True):
|
||||||
|
validity = True
|
||||||
|
|
||||||
|
if len(version) < 1 or len(version) > 16:
|
||||||
|
validity = False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
version = float(version)
|
||||||
|
except ValueError:
|
||||||
|
if not (
|
||||||
|
(version == cls.LATEST_WORKING and latest_working)
|
||||||
|
or (version == cls.LATEST_PUBLISHED and latest_published)
|
||||||
|
):
|
||||||
|
validity = False
|
||||||
|
return validity
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, region_name, recipe_name, recipe_description, recipe_steps, tags
|
self, region_name, recipe_name, recipe_description, recipe_steps, tags
|
||||||
|
):
|
||||||
|
self.versions = OrderedDict()
|
||||||
|
self.versions[self.INITIAL_VERSION] = FakeRecipeVersion(
|
||||||
|
region_name,
|
||||||
|
recipe_name,
|
||||||
|
recipe_description,
|
||||||
|
recipe_steps,
|
||||||
|
tags,
|
||||||
|
version=self.INITIAL_VERSION,
|
||||||
|
)
|
||||||
|
self.latest_working = self.versions[self.INITIAL_VERSION]
|
||||||
|
self.latest_published = None
|
||||||
|
|
||||||
|
def publish(self, description=None):
|
||||||
|
self.latest_published = self.latest_working
|
||||||
|
self.latest_working = deepcopy(self.latest_working)
|
||||||
|
self.latest_published.publish(description)
|
||||||
|
del self.versions[self.latest_working.version]
|
||||||
|
self.versions[self.latest_published.version] = self.latest_published
|
||||||
|
self.latest_working.version = self.latest_published.version + 0.1
|
||||||
|
self.versions[self.latest_working.version] = self.latest_working
|
||||||
|
|
||||||
|
def update(self, description, steps):
|
||||||
|
if description is not None:
|
||||||
|
self.latest_working.description = description
|
||||||
|
if steps is not None:
|
||||||
|
self.latest_working.steps = steps
|
||||||
|
|
||||||
|
def delete_published_version(self, version):
|
||||||
|
version = float(version)
|
||||||
|
assert version.is_integer()
|
||||||
|
if version == self.latest_published.version:
|
||||||
|
prev_version = version - 1.0
|
||||||
|
# Iterate back through the published versions until we find one that exists
|
||||||
|
while prev_version >= 1.0:
|
||||||
|
if prev_version in self.versions:
|
||||||
|
self.latest_published = self.versions[prev_version]
|
||||||
|
break
|
||||||
|
prev_version -= 1.0
|
||||||
|
else:
|
||||||
|
# Didn't find an earlier published version
|
||||||
|
self.latest_published = None
|
||||||
|
del self.versions[version]
|
||||||
|
|
||||||
|
|
||||||
|
class FakeRecipeVersion(BaseModel):
|
||||||
|
def __init__(
|
||||||
|
self, region_name, recipe_name, recipe_description, recipe_steps, tags, version
|
||||||
):
|
):
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.name = recipe_name
|
self.name = recipe_name
|
||||||
@ -128,15 +301,28 @@ class FakeRecipe(BaseModel):
|
|||||||
self.steps = recipe_steps
|
self.steps = recipe_steps
|
||||||
self.created_time = datetime.now()
|
self.created_time = datetime.now()
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
|
self.published_date = None
|
||||||
|
self.version = version
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return {
|
dict_recipe = {
|
||||||
"Name": self.name,
|
"Name": self.name,
|
||||||
"Steps": self.steps,
|
"Steps": self.steps,
|
||||||
"Description": self.description,
|
"Description": self.description,
|
||||||
"CreateTime": self.created_time.isoformat(),
|
"CreateDate": "%.3f" % self.created_time.timestamp(),
|
||||||
"Tags": self.tags or dict(),
|
"Tags": self.tags or dict(),
|
||||||
|
"RecipeVersion": str(self.version),
|
||||||
}
|
}
|
||||||
|
if self.published_date is not None:
|
||||||
|
dict_recipe["PublishedDate"] = "%.3f" % self.published_date.timestamp()
|
||||||
|
|
||||||
|
return dict_recipe
|
||||||
|
|
||||||
|
def publish(self, description):
|
||||||
|
self.version = float(math.ceil(self.version))
|
||||||
|
self.published_date = datetime.now()
|
||||||
|
if description is not None:
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
|
||||||
class FakeRuleset(BaseModel):
|
class FakeRuleset(BaseModel):
|
||||||
|
@ -31,6 +31,22 @@ class DataBrewResponse(BaseResponse):
|
|||||||
).as_dict()
|
).as_dict()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def delete_recipe_version(self, request, full_url, headers):
|
||||||
|
self.setup_class(request, full_url, headers)
|
||||||
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_DeleteRecipeVersion.html
|
||||||
|
if request.method == "DELETE":
|
||||||
|
parsed_url = urlparse(full_url)
|
||||||
|
split_path = parsed_url.path.strip("/").split("/")
|
||||||
|
recipe_name = split_path[1]
|
||||||
|
recipe_version = split_path[3]
|
||||||
|
self.databrew_backend.delete_recipe_version(recipe_name, recipe_version)
|
||||||
|
return (
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
json.dumps({"Name": recipe_name, "RecipeVersion": recipe_version}),
|
||||||
|
)
|
||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def list_recipes(self):
|
def list_recipes(self):
|
||||||
# https://docs.aws.amazon.com/databrew/latest/dg/API_ListRecipes.html
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_ListRecipes.html
|
||||||
@ -38,10 +54,15 @@ class DataBrewResponse(BaseResponse):
|
|||||||
max_results = self._get_int_param(
|
max_results = self._get_int_param(
|
||||||
"MaxResults", self._get_int_param("maxResults")
|
"MaxResults", self._get_int_param("maxResults")
|
||||||
)
|
)
|
||||||
|
recipe_version = self._get_param(
|
||||||
|
"RecipeVersion", self._get_param("recipeVersion")
|
||||||
|
)
|
||||||
|
|
||||||
# pylint: disable=unexpected-keyword-arg, unbalanced-tuple-unpacking
|
# pylint: disable=unexpected-keyword-arg, unbalanced-tuple-unpacking
|
||||||
recipe_list, next_token = self.databrew_backend.list_recipes(
|
recipe_list, next_token = self.databrew_backend.list_recipes(
|
||||||
next_token=next_token, max_results=max_results
|
next_token=next_token,
|
||||||
|
max_results=max_results,
|
||||||
|
recipe_version=recipe_version,
|
||||||
)
|
)
|
||||||
return json.dumps(
|
return json.dumps(
|
||||||
{
|
{
|
||||||
@ -50,19 +71,55 @@ class DataBrewResponse(BaseResponse):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def list_recipe_versions(self, request, full_url, headers):
|
||||||
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_ListRecipeVersions.html
|
||||||
|
self.setup_class(request, full_url, headers)
|
||||||
|
recipe_name = self._get_param("Name", self._get_param("name"))
|
||||||
|
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
|
||||||
|
recipe_list, next_token = self.databrew_backend.list_recipe_versions(
|
||||||
|
recipe_name=recipe_name, next_token=next_token, max_results=max_results
|
||||||
|
)
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"Recipes": [recipe.as_dict() for recipe in recipe_list],
|
||||||
|
"NextToken": next_token,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def publish_recipe(self, request, full_url, headers):
|
||||||
|
self.setup_class(request, full_url, headers)
|
||||||
|
if request.method == "POST":
|
||||||
|
parsed_url = urlparse(full_url)
|
||||||
|
recipe_name = parsed_url.path.strip("/").split("/", 2)[1]
|
||||||
|
recipe_description = self.parameters.get("Description")
|
||||||
|
self.databrew_backend.publish_recipe(recipe_name, recipe_description)
|
||||||
|
return 200, {}, json.dumps({"Name": recipe_name})
|
||||||
|
|
||||||
def put_recipe_response(self, recipe_name):
|
def put_recipe_response(self, recipe_name):
|
||||||
recipe_description = self.parameters.get("Description")
|
recipe_description = self.parameters.get("Description")
|
||||||
recipe_steps = self.parameters.get("Steps")
|
recipe_steps = self.parameters.get("Steps")
|
||||||
tags = self.parameters.get("Tags")
|
|
||||||
|
|
||||||
recipe = self.databrew_backend.update_recipe(
|
self.databrew_backend.update_recipe(
|
||||||
recipe_name, recipe_description, recipe_steps, tags
|
recipe_name, recipe_description, recipe_steps
|
||||||
)
|
)
|
||||||
return 200, {}, json.dumps(recipe.as_dict())
|
return 200, {}, json.dumps({"Name": recipe_name})
|
||||||
|
|
||||||
def get_recipe_response(self, recipe_name):
|
def get_recipe_response(self, recipe_name):
|
||||||
recipe = self.databrew_backend.get_recipe(recipe_name)
|
# https://docs.aws.amazon.com/databrew/latest/dg/API_DescribeRecipe.html
|
||||||
return 201, {}, json.dumps(recipe.as_dict())
|
recipe_version = self._get_param(
|
||||||
|
"RecipeVersion", self._get_param("recipeVersion")
|
||||||
|
)
|
||||||
|
recipe = self.databrew_backend.get_recipe(
|
||||||
|
recipe_name, recipe_version=recipe_version
|
||||||
|
)
|
||||||
|
return 200, {}, json.dumps(recipe.as_dict())
|
||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def recipe_response(self, request, full_url, headers):
|
def recipe_response(self, request, full_url, headers):
|
||||||
|
@ -3,8 +3,11 @@ from .responses import DataBrewResponse
|
|||||||
url_bases = [r"https?://databrew\.(.+)\.amazonaws.com"]
|
url_bases = [r"https?://databrew\.(.+)\.amazonaws.com"]
|
||||||
|
|
||||||
url_paths = {
|
url_paths = {
|
||||||
|
"{0}/recipeVersions$": DataBrewResponse().list_recipe_versions,
|
||||||
"{0}/recipes$": DataBrewResponse.dispatch,
|
"{0}/recipes$": DataBrewResponse.dispatch,
|
||||||
"{0}/recipes/(?P<recipe_name>[^/]+)$": DataBrewResponse().recipe_response,
|
"{0}/recipes/(?P<recipe_name>[^/]+)$": DataBrewResponse().recipe_response,
|
||||||
|
"{0}/recipes/(?P<recipe_name>[^/]+)/recipeVersion/(?P<recipe_version>[^/]+)": DataBrewResponse().delete_recipe_version,
|
||||||
|
"{0}/recipes/(?P<recipe_name>[^/]+)/publishRecipe$": DataBrewResponse().publish_recipe,
|
||||||
"{0}/rulesets$": DataBrewResponse.dispatch,
|
"{0}/rulesets$": DataBrewResponse.dispatch,
|
||||||
"{0}/rulesets/(?P<ruleset_name>[^/]+)$": DataBrewResponse().ruleset_response,
|
"{0}/rulesets/(?P<ruleset_name>[^/]+)$": DataBrewResponse().ruleset_response,
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import uuid
|
|||||||
import boto3
|
import boto3
|
||||||
import pytest
|
import pytest
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from moto import mock_databrew
|
from moto import mock_databrew
|
||||||
|
|
||||||
@ -58,12 +59,30 @@ def test_recipe_list_when_empty():
|
|||||||
response["Recipes"].should.have.length_of(0)
|
response["Recipes"].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_recipe_list_with_invalid_version():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
|
||||||
|
recipe_version = "1.1"
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.list_recipes(RecipeVersion=recipe_version)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
f"Invalid version {recipe_version}. "
|
||||||
|
"Valid versions are LATEST_PUBLISHED and LATEST_WORKING."
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
|
||||||
|
|
||||||
@mock_databrew
|
@mock_databrew
|
||||||
def test_list_recipes_with_max_results():
|
def test_list_recipes_with_max_results():
|
||||||
client = _create_databrew_client()
|
client = _create_databrew_client()
|
||||||
|
|
||||||
_create_test_recipes(client, 4)
|
_create_test_recipes(client, 4)
|
||||||
response = client.list_recipes(MaxResults=2)
|
response = client.list_recipes(MaxResults=2, RecipeVersion="LATEST_WORKING")
|
||||||
response["Recipes"].should.have.length_of(2)
|
response["Recipes"].should.have.length_of(2)
|
||||||
response.should.have.key("NextToken")
|
response.should.have.key("NextToken")
|
||||||
|
|
||||||
@ -72,8 +91,10 @@ def test_list_recipes_with_max_results():
|
|||||||
def test_list_recipes_from_next_token():
|
def test_list_recipes_from_next_token():
|
||||||
client = _create_databrew_client()
|
client = _create_databrew_client()
|
||||||
_create_test_recipes(client, 10)
|
_create_test_recipes(client, 10)
|
||||||
first_response = client.list_recipes(MaxResults=3)
|
first_response = client.list_recipes(MaxResults=3, RecipeVersion="LATEST_WORKING")
|
||||||
response = client.list_recipes(NextToken=first_response["NextToken"])
|
response = client.list_recipes(
|
||||||
|
NextToken=first_response["NextToken"], RecipeVersion="LATEST_WORKING"
|
||||||
|
)
|
||||||
response["Recipes"].should.have.length_of(7)
|
response["Recipes"].should.have.length_of(7)
|
||||||
|
|
||||||
|
|
||||||
@ -81,19 +102,160 @@ def test_list_recipes_from_next_token():
|
|||||||
def test_list_recipes_with_max_results_greater_than_actual_results():
|
def test_list_recipes_with_max_results_greater_than_actual_results():
|
||||||
client = _create_databrew_client()
|
client = _create_databrew_client()
|
||||||
_create_test_recipes(client, 4)
|
_create_test_recipes(client, 4)
|
||||||
response = client.list_recipes(MaxResults=10)
|
response = client.list_recipes(MaxResults=10, RecipeVersion="LATEST_WORKING")
|
||||||
response["Recipes"].should.have.length_of(4)
|
response["Recipes"].should.have.length_of(4)
|
||||||
|
|
||||||
|
|
||||||
@mock_databrew
|
@mock_databrew
|
||||||
def test_describe_recipe():
|
def test_list_recipe_versions_no_recipe():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
recipe_name = "NotExist"
|
||||||
|
response = client.list_recipe_versions(Name=recipe_name)
|
||||||
|
response["Recipes"].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_list_recipe_versions_none_published():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
response = client.list_recipe_versions(Name=recipe_name)
|
||||||
|
response["Recipes"].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_list_recipe_versions_one_published():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
response = client.list_recipe_versions(Name=recipe_name)
|
||||||
|
response["Recipes"].should.have.length_of(1)
|
||||||
|
response["Recipes"][0]["RecipeVersion"].should.equal("1.0")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_list_recipe_versions_two_published():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
response = client.list_recipe_versions(Name=recipe_name)
|
||||||
|
response["Recipes"].should.have.length_of(2)
|
||||||
|
response["Recipes"][0]["RecipeVersion"].should.equal("1.0")
|
||||||
|
response["Recipes"][1]["RecipeVersion"].should.equal("2.0")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_latest_working():
|
||||||
client = _create_databrew_client()
|
client = _create_databrew_client()
|
||||||
response = _create_test_recipe(client)
|
response = _create_test_recipe(client)
|
||||||
|
|
||||||
|
recipe = client.describe_recipe(
|
||||||
|
Name=response["Name"], RecipeVersion="LATEST_WORKING"
|
||||||
|
)
|
||||||
|
|
||||||
|
recipe["Name"].should.equal(response["Name"])
|
||||||
|
recipe["Steps"].should.have.length_of(1)
|
||||||
|
recipe["RecipeVersion"].should.equal("0.1")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_with_version():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
|
||||||
|
recipe = client.describe_recipe(Name=response["Name"], RecipeVersion="0.1")
|
||||||
|
|
||||||
|
recipe["Name"].should.equal(response["Name"])
|
||||||
|
recipe["Steps"].should.have.length_of(1)
|
||||||
|
recipe["RecipeVersion"].should.equal("0.1")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_latest_published():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
|
||||||
|
client.publish_recipe(Name=response["Name"])
|
||||||
|
recipe = client.describe_recipe(
|
||||||
|
Name=response["Name"], RecipeVersion="LATEST_PUBLISHED"
|
||||||
|
)
|
||||||
|
|
||||||
|
recipe["Name"].should.equal(response["Name"])
|
||||||
|
recipe["Steps"].should.have.length_of(1)
|
||||||
|
recipe["RecipeVersion"].should.equal("1.0")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_implicit_latest_published():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
|
||||||
|
client.publish_recipe(Name=response["Name"])
|
||||||
recipe = client.describe_recipe(Name=response["Name"])
|
recipe = client.describe_recipe(Name=response["Name"])
|
||||||
|
|
||||||
recipe["Name"].should.equal(response["Name"])
|
recipe["Name"].should.equal(response["Name"])
|
||||||
recipe["Steps"].should.have.length_of(1)
|
recipe["Steps"].should.have.length_of(1)
|
||||||
|
recipe["RecipeVersion"].should.equal("1.0")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_that_does_not_exist():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.describe_recipe(Name="DoseNotExist")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"The recipe DoseNotExist for version LATEST_PUBLISHED wasn't found."
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_with_long_name():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
name = "a" * 256
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.describe_recipe(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 255"
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_with_long_version():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
version = "1" * 17
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.describe_recipe(Name="AnyName", RecipeVersion=version)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
f"1 validation error detected: Value '{version}' at 'recipeVersion' failed to satisfy constraint: "
|
||||||
|
f"Member must have length less than or equal to 16"
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_describe_recipe_with_invalid_version():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
name = "AnyName"
|
||||||
|
version = "invalid"
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.describe_recipe(Name=name, RecipeVersion=version)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(f"Recipe {name} version {version} isn't valid.")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
@mock_databrew
|
@mock_databrew
|
||||||
@ -129,22 +291,46 @@ def test_update_recipe():
|
|||||||
|
|
||||||
recipe["Name"].should.equal(response["Name"])
|
recipe["Name"].should.equal(response["Name"])
|
||||||
|
|
||||||
# Describe the recipe and change the changes
|
# Describe the recipe and check the changes
|
||||||
recipe = client.describe_recipe(Name=response["Name"])
|
recipe = client.describe_recipe(
|
||||||
|
Name=response["Name"], RecipeVersion="LATEST_WORKING"
|
||||||
|
)
|
||||||
recipe["Name"].should.equal(response["Name"])
|
recipe["Name"].should.equal(response["Name"])
|
||||||
recipe["Steps"].should.have.length_of(1)
|
recipe["Steps"].should.have.length_of(1)
|
||||||
recipe["Steps"][0]["Action"]["Parameters"]["removeCustomValue"].should.equal("true")
|
recipe["Steps"][0]["Action"]["Parameters"]["removeCustomValue"].should.equal("true")
|
||||||
|
|
||||||
|
|
||||||
@mock_databrew
|
@mock_databrew
|
||||||
def test_describe_recipe_that_does_not_exist():
|
def test_update_recipe_description():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
|
||||||
|
description = "NewDescription"
|
||||||
|
recipe = client.update_recipe(
|
||||||
|
Name=response["Name"], Steps=[], Description=description
|
||||||
|
)
|
||||||
|
|
||||||
|
recipe["Name"].should.equal(response["Name"])
|
||||||
|
|
||||||
|
# Describe the recipe and check the changes
|
||||||
|
recipe = client.describe_recipe(
|
||||||
|
Name=response["Name"], RecipeVersion="LATEST_WORKING"
|
||||||
|
)
|
||||||
|
recipe["Name"].should.equal(response["Name"])
|
||||||
|
recipe["Description"].should.equal(description)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_update_recipe_invalid():
|
||||||
client = _create_databrew_client()
|
client = _create_databrew_client()
|
||||||
|
|
||||||
|
recipe_name = "NotFound"
|
||||||
with pytest.raises(ClientError) as exc:
|
with pytest.raises(ClientError) as exc:
|
||||||
client.describe_recipe(Name="DoseNotExist")
|
client.update_recipe(Name=recipe_name)
|
||||||
err = exc.value.response["Error"]
|
err = exc.value.response["Error"]
|
||||||
err["Code"].should.equal("EntityNotFoundException")
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
err["Message"].should.equal("Recipe DoseNotExist not found.")
|
err["Message"].should.equal(f"The recipe {recipe_name} wasn't found")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
|
||||||
|
|
||||||
|
|
||||||
@mock_databrew
|
@mock_databrew
|
||||||
@ -152,9 +338,194 @@ def test_create_recipe_that_already_exists():
|
|||||||
client = _create_databrew_client()
|
client = _create_databrew_client()
|
||||||
|
|
||||||
response = _create_test_recipe(client)
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
with pytest.raises(ClientError) as exc:
|
with pytest.raises(ClientError) as exc:
|
||||||
_create_test_recipe(client, recipe_name=response["Name"])
|
_create_test_recipe(client, recipe_name=response["Name"])
|
||||||
err = exc.value.response["Error"]
|
err = exc.value.response["Error"]
|
||||||
err["Code"].should.equal("AlreadyExistsException")
|
err["Code"].should.equal("ConflictException")
|
||||||
err["Message"].should.equal("Recipe already exists.")
|
err["Message"].should.equal(f"The recipe {recipe_name} already exists")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_publish_recipe():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
|
||||||
|
# Before a recipe is published, we should not be able to retrieve a published version
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
recipe = client.describe_recipe(Name=recipe_name)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
dt_before_publish = datetime.now().astimezone()
|
||||||
|
|
||||||
|
# Publish the recipe
|
||||||
|
publish_response = client.publish_recipe(Name=recipe_name, Description="1st desc")
|
||||||
|
publish_response["Name"].should.equal(recipe_name)
|
||||||
|
|
||||||
|
# Recipe is now published, so check we can retrieve the published version
|
||||||
|
recipe = client.describe_recipe(Name=recipe_name)
|
||||||
|
recipe["Description"].should.equal("1st desc")
|
||||||
|
recipe["RecipeVersion"].should.equal("1.0")
|
||||||
|
recipe["PublishedDate"].should.be.greater_than(dt_before_publish)
|
||||||
|
first_published_date = recipe["PublishedDate"]
|
||||||
|
|
||||||
|
# Publish the recipe a 2nd time
|
||||||
|
publish_response = client.publish_recipe(Name=recipe_name, Description="2nd desc")
|
||||||
|
publish_response["Name"].should.equal(recipe_name)
|
||||||
|
|
||||||
|
recipe = client.describe_recipe(Name=recipe_name)
|
||||||
|
recipe["Description"].should.equal("2nd desc")
|
||||||
|
recipe["RecipeVersion"].should.equal("2.0")
|
||||||
|
recipe["PublishedDate"].should.be.greater_than(first_published_date)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_publish_recipe_that_does_not_exist():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.publish_recipe(Name="DoesNotExist")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_publish_long_recipe_name():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
name = "a" * 256
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.publish_recipe(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 255"
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion="LATEST_WORKING")
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.describe_recipe(Name=recipe_name)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version_published():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion="1.0")
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.describe_recipe(Name=recipe_name)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
|
||||||
|
recipe = client.describe_recipe(Name=recipe_name, RecipeVersion="1.1")
|
||||||
|
recipe["RecipeVersion"].should.equal("1.1")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version_latest_working_after_publish():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion="LATEST_WORKING")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"Recipe version LATEST_WORKING is not allowed to be deleted"
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version_latest_working_numeric_after_publish():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion="1.1")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal("Recipe version 1.1 is not allowed to be deleted")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version_invalid_version_string():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
recipe_version = "NotValid"
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion=recipe_version)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
f"Recipe {recipe_name} version {recipe_version} is invalid."
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version_invalid_version_length():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
recipe_version = "1" * 17
|
||||||
|
client.publish_recipe(Name=recipe_name)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion=recipe_version)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
f"Recipe {recipe_name} version {recipe_version} is invalid."
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version_unknown_recipe():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
recipe_name = "Unknown"
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion="1.1")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
err["Message"].should.equal(f"The recipe {recipe_name} wasn't found")
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_databrew
|
||||||
|
def test_delete_recipe_version_unknown_version():
|
||||||
|
client = _create_databrew_client()
|
||||||
|
response = _create_test_recipe(client)
|
||||||
|
recipe_name = response["Name"]
|
||||||
|
recipe_version = "1.1"
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.delete_recipe_version(Name=recipe_name, RecipeVersion=recipe_version)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
f"The recipe {recipe_name} version {recipe_version} wasn't found."
|
||||||
|
)
|
||||||
|
exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user