Repository: cloudtools/troposphere (python)

Issue:
GlobalsHelperFn type validation crashes for list types or validation functions
### Problem statement
The `*Globals` classes defined in https://github.com/cloudtools/troposphere/blob/main/troposphere/serverless.py have some properties with their type specified as `[T]` or as validation functions (e.g. validate_memory_size). They all inherit from [GlobalsHelperFn](https://github.com/cloudtools/troposphere/blob/main/troposphere/serverless.py#L659) which does not handle those types during runtime type validation, all it does is feed them into `isinstance` which ends up throwing:
```
TypeError: isinstance() arg 2 must be a type or tuple of types
```

### Steps to reproduce
```py
from troposphere.serverless import FunctionGlobals
FunctionGlobals(MemorySize=3072)
```

---

I've noticed [BaseAWSObject](https://github.com/cloudtools/troposphere/blob/main/troposphere/__init__.py#L141) has proper handling for such types [here for validation functions](https://github.com/cloudtools/troposphere/blob/main/troposphere/__init__.py#L257) and [here for lists](https://github.com/cloudtools/troposphere/blob/main/troposphere/__init__.py#L270). Something similar needs to be used for the Globals.

Pull request:
Add support for list types and validator functions in GlobalsHelperFn type check
Closes #2063

I tried extracting the  function to `troposhpere.validators.__init__.py` as it made more sense to have it there, but its dependence on `AWSHelperFn` created a circular import with `troposhpere.__init__.py`. Fixing that would require picking apart the entire `troposhpere.__init__.py` file.

Patch:
diff --git a/troposphere/serverless.py b/troposphere/serverless.py
index 18f153bc..452c4d49 100644
--- a/troposphere/serverless.py
+++ b/troposphere/serverless.py
@@ -656,31 +656,7 @@ class Application(AWSObject):
         mutually_exclusive(self.__class__.__name__, self.properties, conds)
 
 
-class GlobalsHelperFn(AWSHelperFn):
-    def __init__(self, **kwargs):
-        remaining_kwargs = kwargs.copy()
-        for k, (key_type, required) in self.props.items():
-            if k in kwargs:
-                kwarg_value = remaining_kwargs.pop(k)
-                if not isinstance(kwarg_value, key_type):
-                    raise ValueError(
-                        f"Provided {self.__class__.__name__} property '{k}' is of type {type(kwarg_value)} instead of expected {key_type}"
-                    )
-            elif required:
-                raise ValueError(
-                    f"Required {self.__class__.__name__} property '{k}' not provided"
-                )
-
-        if remaining_kwargs:
-            unexpected_properties = [f"'{k}'" for k in remaining_kwargs.keys()]
-            raise ValueError(
-                f"Unexpected {self.__class__.__name__} properties provided: {unexpected_properties}"
-            )
-
-        self.data = kwargs
-
-
-class FunctionGlobals(GlobalsHelperFn):
+class FunctionGlobals(AWSProperty):
     props: PropsDictType = {
         "AssumeRolePolicyDocument": (policytypes, False),
         "AutoPublishAlias": (str, False),
@@ -706,7 +682,7 @@ class FunctionGlobals(GlobalsHelperFn):
     }
 
 
-class ApiGlobals(GlobalsHelperFn):
+class ApiGlobals(AWSProperty):
     props: PropsDictType = {
         "AccessLogSetting": (AccessLogSetting, False),
         "Auth": (Auth, False),
@@ -727,7 +703,7 @@ class ApiGlobals(GlobalsHelperFn):
     }
 
 
-class HttpApiGlobals(GlobalsHelperFn):
+class HttpApiGlobals(AWSProperty):
     props: PropsDictType = {
         "AccessLogSettings": (AccessLogSettings, False),
         "Auth": (HttpApiAuth, False),
@@ -736,13 +712,13 @@ class HttpApiGlobals(GlobalsHelperFn):
     }
 
 
-class SimpleTableGlobals(GlobalsHelperFn):
+class SimpleTableGlobals(AWSProperty):
     props: PropsDictType = {
         "SSESpecification": (SSESpecification, False),
     }
 
 
-class Globals(GlobalsHelperFn):
+class Globals(AWSProperty):
     """Supported Globals properties.
 
     See: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html


Test patch:
diff --git a/tests/test_serverless.py b/tests/test_serverless.py
index f4fcac04..1ca0bc70 100644
--- a/tests/test_serverless.py
+++ b/tests/test_serverless.py
@@ -431,10 +431,22 @@ class TestServerless(unittest.TestCase):
         )
         t.to_json()
 
+        with self.assertRaises(AttributeError):
+            Globals(Unexpected="blah")
+        with self.assertRaises(TypeError):
+            Globals(Function="not FunctionGlobals")
+
+        # Assert list types are validated correctly
+        FunctionGlobals(Layers=["test"])
+        with self.assertRaises(TypeError):
+            FunctionGlobals(Layers="not a list")
+        with self.assertRaises(TypeError):
+            FunctionGlobals(Layers=[1, 2, 3])
+
+        # Assert function validators work as intended
+        FunctionGlobals(MemorySize=128)
         with self.assertRaises(ValueError):
-            _ = Globals(Unexpected="blah")
-        with self.assertRaises(ValueError):
-            _ = Globals(Function="not FunctionGlobals")
+            FunctionGlobals(MemorySize=64)
 
 
 if __name__ == "__main__":