Skip to content

Commit e4b6e77

Browse files
authored
feat: allow classes that inherit Resource to disable __setattr__ validation (#3047)
1 parent bfcb243 commit e4b6e77

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

samtranslator/model/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class Resource(ABC):
105105
# TODO: Make `Resource` an abstract class and not giving `resource_type`/`property_types` initial value.
106106
resource_type: str = None # type: ignore
107107
property_types: Dict[str, PropertyType] = None # type: ignore
108-
_keywords = ["logical_id", "relative_id", "depends_on", "resource_attributes"]
108+
_keywords = {"logical_id", "relative_id", "depends_on", "resource_attributes"}
109109

110110
# For attributes in this list, they will be passed into the translated template for the same resource itself.
111111
_supported_resource_attributes = ["DeletionPolicy", "UpdatePolicy", "Condition", "UpdateReplacePolicy", "Metadata"]
@@ -122,6 +122,11 @@ class Resource(ABC):
122122
# }
123123
runtime_attrs: Dict[str, Callable[["Resource"], Any]] = {} # TODO: replace Any with something more explicit
124124

125+
# When "validate_setattr" is True, we cannot change the value of any class variables after instantiation unless they
126+
# are in "property_types" or "_keywords". We can set this to False in the inheriting class definition so we can
127+
# update other class variables as well after instantiation.
128+
validate_setattr: bool = True
129+
125130
def __init__(
126131
self,
127132
logical_id: Optional[Any],
@@ -313,7 +318,7 @@ def __setattr__(self, name, value): # type: ignore[no-untyped-def]
313318
:param value: the value of the attribute to be set
314319
:raises InvalidResourceException: if an invalid property is provided
315320
"""
316-
if name in self._keywords or name in self.property_types:
321+
if (name in self._keywords or name in self.property_types) or not self.validate_setattr:
317322
return super().__setattr__(name, value)
318323

319324
raise InvalidResourceException(

tests/model/test_resource.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from unittest import TestCase
2+
3+
from samtranslator.model import Property, SamResourceMacro
4+
from samtranslator.model.exceptions import InvalidResourceException
5+
from samtranslator.model.types import IS_STR
6+
7+
8+
class DummyResourceWithValidation(SamResourceMacro):
9+
# validate_setattr is set to True in Resource class by default
10+
resource_type = "AWS::Serverless:DummyResource"
11+
property_types = {"SomeProperty": Property(False, IS_STR), "AnotherProperty": Property(False, IS_STR)}
12+
13+
def to_cloudformation(self, **kwargs):
14+
return []
15+
16+
17+
class DummyResourceNoValidation(SamResourceMacro):
18+
resource_type = "AWS::Serverless:DummyResource"
19+
property_types = {"SomeProperty": Property(False, IS_STR), "AnotherProperty": Property(False, IS_STR)}
20+
21+
validate_setattr = False
22+
23+
def to_cloudformation(self, **kwargs):
24+
return []
25+
26+
27+
class TestResource(TestCase):
28+
def test_create_instance_variable_when_validate_setattr_is_true(self):
29+
resource = DummyResourceWithValidation("foo")
30+
with self.assertRaises(InvalidResourceException):
31+
resource.SomeExtraValue = "foo"
32+
33+
def test_create_instance_variable_when_validate_setattr_is_false(self):
34+
resource = DummyResourceNoValidation("foo")
35+
36+
resource.SomeExtraValue = "foo"
37+
resource.AnotherValue = "bar"
38+
resource.RandomValue = "baz"

0 commit comments

Comments
 (0)