# Buggy code
def create_dict_node_class(cls):
    """
    Create dynamic node class
    """

    class node_class(cls):
        """Node class created based on the input class"""

        def __init__(self, x, start_mark, end_mark):
            try:
                cls.__init__(self, x)
            except TypeError:
                cls.__init__(self)
            self.start_mark = start_mark
            self.end_mark = end_mark
            self.condition_functions = ["Fn::If"]

        def __deepcopy__(self, memo):
            result = dict_node(self, self.start_mark, self.end_mark)
            memo[id(self)] = result
            for k, v in self.items():
                result[deepcopy(k)] = deepcopy(v, memo)

            return result

        def __copy__(self):
            return self

        def is_function_returning_object(self, mappings=None):
            """
            Check if an object is using a function that could return an object
            Return True when
                Fn::Select:
                - 0  # or any number
                - !FindInMap [mapname, key, value] # or any mapname, key, value
            Otherwise False
            """
            mappings = mappings or {}
            if len(self) == 1:
                for k, v in self.items():
                    if k in ["Fn::Select"]:
                        if isinstance(v, list):
                            if len(v) == 2:
                                p_v = v[1]
                                if isinstance(p_v, dict):
                                    if len(p_v) == 1:
                                        for l_k in p_v.keys():
                                            if l_k == "Fn::FindInMap":
                                                return True

            return False

        def get(self, key, default=None):
            """Override the default get"""
            if isinstance(default, dict):
                default = dict_node(default, self.start_mark, self.end_mark)
            return super(node_class, self).get(key, default)

        def get_safe(self, key, default=None, path=None, type_t=()):
            """
            Get values in format
            """
            path = path or []
            value = self.get(key, default)
            if not isinstance(value, (dict)):
                if isinstance(value, type_t) or not type_t:
                    return [(value, (path[:] + [key]))]

            results = []
            for sub_v, sub_path in value.items_safe(path + [key]):
                if isinstance(sub_v, type_t) or not type_t:
                    results.append((sub_v, sub_path))

            return results

        def items_safe(self, path=None, type_t=()):
            """Get items while handling IFs"""
            path = path or []
            if len(self) == 1:
                for k, v in self.items():
                    if k == "Fn::If":
                        if isinstance(v, list):
                            if len(v) == 3:
                                for i, if_v in enumerate(v[1:]):
                                    if isinstance(if_v, dict):
                                        # yield from if_v.items_safe(path[:] + [k, i - 1])
                                        # Python 2.7 support
                                        for items, p in if_v.items_safe(
                                            path[:] + [k, i + 1]
                                        ):
                                            if isinstance(items, type_t) or not type_t:
                                                yield items, p
                                    elif isinstance(if_v, list):
                                        if isinstance(if_v, type_t) or not type_t:
                                            yield if_v, path[:] + [k, i + 1]
                                    else:
                                        if isinstance(if_v, type_t) or not type_t:
                                            yield if_v, path[:] + [k, i + 1]
                    elif not (k == "Ref" and v == "AWS::NoValue"):
                        if isinstance(self, type_t) or not type_t:
                            yield self, path[:]
            else:
                if isinstance(self, type_t) or not type_t:
                    yield self, path[:]

        def __getattr__(self, name):
            raise TemplateAttributeError(
                "%s.%s is invalid" % (self.__class__.__name__, name)
            )

    node_class.__name__ = "%s_node" % cls.__name__
    return node_class


# Traceback (AttributeError)
2020-02-17 15:24:57,775 - cfnlint - DEBUG - Completed linting of file: .\test.yml
E0002 Unknown exception while processing rule E3028: Traceback (most recent call last):
File "c:\python36\lib\site-packages\cfnlint\rules\__init__.py", line 203, in run_check
return check(*args)
File "c:\python36\lib\site-packages\cfnlint\rules\__init__.py", line 89, in wrapper
results = match_function(self, filename, cfn, *args, **kwargs)
File "c:\python36\lib\site-packages\cfnlint\rules\__init__.py", line 122, in matchall_resource_properties
return self.match_resource_properties(resource_properties, property_type, path, cfn)  # pylint: disable=E1102
File "c:\python36\lib\site-packages\cfnlint\rules\resources\rds\AuroraScalingConfiguration.py", line 44, in match_resource_properties
matches.extend(self.check(properties, path, cfn))
File "c:\python36\lib\site-packages\cfnlint\rules\resources\rds\AuroraScalingConfiguration.py", line 30, in check
engine = properties.get_safe('EngineMode', type_t=six.string_types)
File "c:\python36\lib\site-packages\cfnlint\decode\node.py", line 125, in get_safe
for sub_v, sub_path in value.items_safe(path + [key]):
AttributeError: 'NoneType' object has no attribute 'items_safe'
.\test.yml:1:1

# Fixed code
def create_dict_node_class(cls):
    """
    Create dynamic node class
    """

    class node_class(cls):
        """Node class created based on the input class"""

        def __init__(self, x, start_mark, end_mark):
            try:
                cls.__init__(self, x)
            except TypeError:
                cls.__init__(self)
            self.start_mark = start_mark
            self.end_mark = end_mark
            self.condition_functions = ["Fn::If"]

        def __deepcopy__(self, memo):
            result = dict_node(self, self.start_mark, self.end_mark)
            memo[id(self)] = result
            for k, v in self.items():
                result[deepcopy(k)] = deepcopy(v, memo)

            return result

        def __copy__(self):
            return self

        def is_function_returning_object(self, mappings=None):
            """
            Check if an object is using a function that could return an object
            Return True when
                Fn::Select:
                - 0  # or any number
                - !FindInMap [mapname, key, value] # or any mapname, key, value
            Otherwise False
            """
            mappings = mappings or {}
            if len(self) == 1:
                for k, v in self.items():
                    if k in ["Fn::Select"]:
                        if isinstance(v, list):
                            if len(v) == 2:
                                p_v = v[1]
                                if isinstance(p_v, dict):
                                    if len(p_v) == 1:
                                        for l_k in p_v.keys():
                                            if l_k == "Fn::FindInMap":
                                                return True

            return False

        def get(self, key, default=None):
            """Override the default get"""
            if isinstance(default, dict):
                default = dict_node(default, self.start_mark, self.end_mark)
            return super(node_class, self).get(key, default)

        def get_safe(self, key, default=None, path=None, type_t=()):
            """
            Get values in format
            """
            path = path or []
            value = self.get(key, default)
            if value is None and default is None:
                # if default is None and value is None return empty list
                return []

            # if the value is the default make sure that the default value is of type_t when specified
            if bool(type_t) and value == default and not isinstance(default, type_t):
                raise ValueError('"default" type should be of "type_t"')

            # when not a dict see if if the value is of the right type
            results = []
            if not isinstance(value, (dict)):
                if isinstance(value, type_t) or not type_t:
                    return [(value, (path[:] + [key]))]
            else:
                for sub_v, sub_path in value.items_safe(path + [key]):
                    if isinstance(sub_v, type_t) or not type_t:
                        results.append((sub_v, sub_path))

            return results

        def items_safe(self, path=None, type_t=()):
            """Get items while handling IFs"""
            path = path or []
            if len(self) == 1:
                for k, v in self.items():
                    if k == "Fn::If":
                        if isinstance(v, list):
                            if len(v) == 3:
                                for i, if_v in enumerate(v[1:]):
                                    if isinstance(if_v, dict):
                                        # yield from if_v.items_safe(path[:] + [k, i - 1])
                                        # Python 2.7 support
                                        for items, p in if_v.items_safe(
                                            path[:] + [k, i + 1]
                                        ):
                                            if isinstance(items, type_t) or not type_t:
                                                yield items, p
                                    elif isinstance(if_v, list):
                                        if isinstance(if_v, type_t) or not type_t:
                                            yield if_v, path[:] + [k, i + 1]
                                    else:
                                        if isinstance(if_v, type_t) or not type_t:
                                            yield if_v, path[:] + [k, i + 1]
                    elif not (k == "Ref" and v == "AWS::NoValue"):
                        if isinstance(self, type_t) or not type_t:
                            yield self, path[:]
            else:
                if isinstance(self, type_t) or not type_t:
                    yield self, path[:]

        def __getattr__(self, name):
            raise TemplateAttributeError(
                "%s.%s is invalid" % (self.__class__.__name__, name)
            )

    node_class.__name__ = "%s_node" % cls.__name__
    return node_class