Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions src/cfnlint/context/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from collections import deque
from copy import deepcopy
from dataclasses import InitVar, dataclass, field, fields
from functools import lru_cache
from functools import cached_property, lru_cache
from typing import TYPE_CHECKING, Any, Deque, Iterator, Set, Tuple

import regex as re
Expand Down Expand Up @@ -191,18 +191,13 @@ class Context:
# exceptions will affect the results.
allow_exceptions: bool = field(init=True, default=True)

# Cached: logical IDs of MODULE-type resources
module_names: tuple[str, ...] = field(init=False, default=())

def __post_init__(self) -> None:
object.__setattr__(
self,
"module_names",
tuple(
name
for name, resource in self.resources.items()
if resource.type.endswith("::MODULE")
),
@cached_property
def module_names(self) -> tuple[str, ...]:
"""Logical IDs of MODULE-type resources (lazily computed and cached)"""
return tuple(
name
for name, resource in self.resources.items()
if resource.type.endswith("::MODULE")
)

def evolve(self, **kwargs) -> "Context":
Expand Down
53 changes: 53 additions & 0 deletions test/unit/module/context/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from cfnlint.context import Context, Path
from cfnlint.context.conditions._conditions import Condition, Conditions
from cfnlint.context.context import Resource


class TestCfnContext(unittest.TestCase):
Expand Down Expand Up @@ -106,3 +107,55 @@ def test_class(self):
{"Foo": True},
),
)


class _CountingResources(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.items_calls = 0

def items(self):
self.items_calls += 1
return super().items()


class TestModuleNames(unittest.TestCase):
def test_module_names(self):
context = Context(
resources={
"MyModule": Resource({"Type": "My::Organization::Custom::MODULE"}),
"MyBucket": Resource({"Type": "AWS::S3::Bucket"}),
}
)
self.assertEqual(context.module_names, ("MyModule",))

def test_module_names_not_computed_on_evolve(self):
# evolve() constructs a new Context on nearly every schema-walk
# descend, so neither construction nor evolution may scan resources;
# the scan happens lazily on first read and is cached per instance
resources = _CountingResources(
{
"MyModule": Resource({"Type": "My::Organization::Custom::MODULE"}),
"MyBucket": Resource({"Type": "AWS::S3::Bucket"}),
}
)
context = Context(resources=resources)
evolved = context.evolve(regions=["us-west-2"]).evolve(path=Path())
self.assertEqual(resources.items_calls, 0)

self.assertEqual(evolved.module_names, ("MyModule",))
self.assertEqual(resources.items_calls, 1)
self.assertEqual(evolved.module_names, ("MyModule",))
self.assertEqual(resources.items_calls, 1)

def test_module_names_recomputed_when_resources_replaced(self):
context = Context(
resources={
"MyModule": Resource({"Type": "My::Organization::Custom::MODULE"}),
}
)
self.assertEqual(context.module_names, ("MyModule",))
replaced = context.evolve(
resources={"MyBucket": Resource({"Type": "AWS::S3::Bucket"})}
)
self.assertEqual(replaced.module_names, ())
Loading