Skip to content

Commit

Permalink
[Resolves #570] Improve Stack.__repr__()
Browse files Browse the repository at this point in the history
Previously, dict and list types were contained within strings in
sceptre.stack.Stack.__repr__(). This commit removes the strings so that
a stack.__repr__() can be used to create a stack using eval().

In order to properly compare stacks a custom `__eq__` and therefore
`__hash__` implementations were added to Stack. Adding a correct `__eq__`
method to Stack allowed testing of Stack objects that were created from
`eval()` of the stack `__repr__`.
  • Loading branch information
ngfgrant committed Jan 16, 2019
1 parent 74ba757 commit df9fc20
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 34 deletions.
64 changes: 49 additions & 15 deletions sceptre/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,21 +145,27 @@ def __init__(
def __repr__(self):
return (
"sceptre.stack.Stack("
"name='{name}', project_code='{project_code}', "
"template_path='{template_path}', region='{region}', "
"template_bucket_name='{template_bucket_name}', "
"template_key_prefix='{template_key_prefix}', "
"required_version='{required_version}', "
"profile='{profile}', "
"sceptre_user_data='{sceptre_user_data}', "
"parameters='{parameters}', "
"hooks='{hooks}', s3_details='{s3_details}', "
"dependencies='{dependencies}', role_arn='{role_arn}', "
"protected='{protected}', tags='{tags}', "
"external_name='{external_name}', "
"notifications='{notifications}', on_failure='{on_failure}', "
"stack_timeout='{stack_timeout}', "
"stack_group_config='{stack_group_config}'"
"name={name}, "
"project_code={project_code}, "
"template_path={template_path}, "
"region={region}, "
"template_bucket_name={template_bucket_name}, "
"template_key_prefix={template_key_prefix}, "
"required_version={required_version}, "
"profile={profile}, "
"sceptre_user_data={sceptre_user_data}, "
"parameters={parameters}, "
"hooks={hooks}, "
"s3_details={s3_details}, "
"dependencies={dependencies}, "
"role_arn={role_arn}, "
"protected={protected}, "
"tags={tags}, "
"external_name={external_name}, "
"notifications={notifications}, "
"on_failure={on_failure}, "
"stack_timeout={stack_timeout}, "
"stack_group_config={stack_group_config}"
")".format(
name=self.name,
project_code=self.project_code,
Expand Down Expand Up @@ -188,6 +194,34 @@ def __repr__(self):
def __str__(self):
return self.name

def __eq__(self, stack):
return (
self.name == stack.name and
self.project_code == stack.project_code and
self.template_path == stack.template_path and
self.region == stack.region and
self.template_bucket_name == stack.template_bucket_name and
self.template_key_prefix == stack.template_key_prefix and
self.required_version == stack.required_version and
self.profile == stack.profile and
self.sceptre_user_data == stack.sceptre_user_data and
self.parameters == stack.parameters and
self.hooks == stack.hooks and
self.s3_details == stack.s3_details and
self.dependencies == stack.dependencies and
self.role_arn == stack.role_arn and
self.protected == stack.protected and
self.tags == stack.tags and
self.external_name == stack.external_name and
self.notifications == stack.notifications and
self.on_failure == stack.on_failure and
self.stack_timeout == stack.stack_timeout and
self.stack_group_config == stack.stack_group_config
)

def __hash__(self):
return hash(str(self))

@property
def template(self):
"""
Expand Down
54 changes: 35 additions & 19 deletions tests/test_stack.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-

import importlib
from mock import sentinel, MagicMock

from sceptre.stack import Stack
Expand Down Expand Up @@ -59,23 +60,38 @@ def test_initiate_stack(self):
def test_repr(self):
assert self.stack.__repr__() == \
"sceptre.stack.Stack(" \
"name='sentinel.stack_name', " \
"project_code='sentinel.project_code', " \
"template_path='sentinel.template_path', " \
"region='sentinel.region', " \
"template_bucket_name='sentinel.template_bucket_name', "\
"template_key_prefix='sentinel.template_key_prefix', "\
"required_version='sentinel.required_version', "\
"profile='sentinel.profile', " \
"sceptre_user_data='sentinel.sceptre_user_data', " \
"parameters='{'key1': 'val1'}', "\
"hooks='{}', s3_details='None', " \
"dependencies='sentinel.dependencies', "\
"role_arn='sentinel.role_arn', " \
"protected='False', tags='{'tag1': 'val1'}', " \
"external_name='sentinel.external_name', " \
"notifications='[sentinel.notification]', " \
"on_failure='sentinel.on_failure', " \
"stack_timeout='sentinel.stack_timeout', " \
"stack_group_config='{}'" \
"name=sentinel.stack_name, " \
"project_code=sentinel.project_code, " \
"template_path=sentinel.template_path, " \
"region=sentinel.region, " \
"template_bucket_name=sentinel.template_bucket_name, "\
"template_key_prefix=sentinel.template_key_prefix, "\
"required_version=sentinel.required_version, "\
"profile=sentinel.profile, " \
"sceptre_user_data=sentinel.sceptre_user_data, " \
"parameters={'key1': 'val1'}, "\
"hooks={}, "\
"s3_details=None, " \
"dependencies=sentinel.dependencies, "\
"role_arn=sentinel.role_arn, "\
"protected=False, "\
"tags={'tag1': 'val1'}, "\
"external_name=sentinel.external_name, " \
"notifications=[sentinel.notification], " \
"on_failure=sentinel.on_failure, " \
"stack_timeout=sentinel.stack_timeout, " \
"stack_group_config={}" \
")"

def test_repr_can_eval_correctly(self):
sceptre = importlib.import_module('sceptre')
mock = importlib.import_module('mock')
evaluated_stack = eval(
repr(self.stack),
{
'sceptre': sceptre,
'sentinel': mock.mock.sentinel
}
)
assert isinstance(evaluated_stack, Stack)
assert evaluated_stack.__eq__(self.stack)

0 comments on commit df9fc20

Please sign in to comment.