cloudtools / troposphere

troposphere - Python library to create AWS CloudFormation descriptions
BSD 2-Clause "Simplified" License
4.93k stars 1.45k forks source link

TypeError: unhashable type: 'SecurityGroup' #2204

Closed bogdankatishev closed 11 months ago

bogdankatishev commented 11 months ago

Hello, maybe also slightly related to #2196, since upgrading from 4.4.x to 4.5.0/4.5.1, we are receiving this compile error:

TypeError: unhashable type: 'SecurityGroup'

Minimal code to reproduce:

from troposphere.ec2 import SecurityGroup

test_sg = SecurityGroup(
    "TestSg",
    VpcId="myVPC",
    GroupDescription="Test Security Group",
)

{ test_sg }

We also have been relying multiple years on the SecurityGroup as keys to dict.

What to do now?

markpeek commented 11 months ago

Try this patch that moves the __hash__ function to BaseAWSObject:

$ git diff
diff --git a/tests/test_basic.py b/tests/test_basic.py
index f5714b1..7a03842 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -122,6 +122,17 @@ class TestBasic(unittest.TestCase):
         assert Parameter("param1") != Parameter("param2")
         assert Stack("stack1") != Stack("stack2")

+    def test___hash__(self):
+        """Test __hash__."""
+        from troposphere.ec2 import SecurityGroup
+
+        test_sg = SecurityGroup(
+            "TestSg",
+            VpcId="myVPC",
+            GroupDescription="Test Security Group",
+        )
+        _ = hash(test_sg)
+
     def test_badproperty(self):
         with self.assertRaises(AttributeError):
             Instance(
diff --git a/troposphere/__init__.py b/troposphere/__init__.py
index 1f214e2..da86b49 100644
--- a/troposphere/__init__.py
+++ b/troposphere/__init__.py
@@ -431,6 +431,9 @@ class BaseAWSObject:
     def __ne__(self, other: object) -> bool:
         return not self == other

+    def __hash__(self) -> int:
+        return hash(json.dumps({"title": self.title, **self.to_dict()}, indent=0))
+

 class AWSObject(BaseAWSObject):
     dictname = "Properties"
@@ -1115,6 +1118,3 @@ class Parameter(AWSDeclaration):
                         "%s can only be used with parameters of "
                         "the CommaDelimitedList type." % p
                     )
-
-    def __hash__(self) -> int:
-        return hash(json.dumps({"title": self.title, **self.to_dict()}, indent=0))
$ cat sghash.py
from troposphere.ec2 import SecurityGroup

test_sg = SecurityGroup(
    "TestSg",
    VpcId="myVPC",
    GroupDescription="Test Security Group",
)

{ test_sg }
$ python sghash.py
$
zipkid commented 11 months ago

@markpeek I have used your diff and for me it seems to work. I have tested the minimal code & 24 random scripts from our repo and they all build without error.

markpeek commented 10 months ago

Fix is in Release 4.5.2