Coverage for jinja2_async_environment/loaders/dict.py: 83%

1"""Async dictionary template loader implementation."""

2

3 import typing as t

4

5 from jinja2.utils import internalcode

6

7 from .base import AsyncBaseLoader, SourceType

8

9 if t.TYPE_CHECKING:

10 from ..environment import AsyncEnvironment

11

12

13 class AsyncDictLoader(AsyncBaseLoader):

14 """Async dictionary template loader with memory optimization.

15

16 This loader stores templates in memory as a dictionary, useful for

17 testing and applications that generate templates dynamically.

18 """

19

20 __slots__ = ("mapping",)

21

22 def __init__(

23 self,

24 mapping: dict[str, str],

25 searchpath: t.Any = None, # For backward compatibility

26 ) -> None:

27 """Initialize the dictionary loader.

28

29 Args:

30 mapping: Dictionary mapping template names to template source

31 searchpath: Path or sequence of paths for compatibility

32 """

33 # Call parent with provided searchpath

34 if searchpath is not None:

35 super().__init__(searchpath)

36 else:

37 # Call parent with empty searchpath for backward compatibility

38 super().__init__([])

39 self.mapping = dict(mapping) # Create a copy for safety

40

41 @internalcode

42 async def get_source_async(

43 self, environment: "AsyncEnvironment", name: str

44 ) -> SourceType:

45 """Get template source from dictionary asynchronously with caching.

46

47 Args:

48 environment: The async environment instance

49 name: Template name to load

50

51 Returns:

52 Tuple of (source, filename, uptodate_func)

53

54 Raises:

55 TemplateNotFound: If template is not in the mapping

56 """

57 self._ensure_initialized()

58

59 # Try to get from cache first

60 cache_manager = self._get_cache_manager(environment)

61 cache_key = f"dict:{name}"

62

63 if cache_manager:

64 cached_source = cache_manager.get("template", cache_key)

65 if cached_source is not None:

66 return cached_source

67

68 if name not in self.mapping:

69 self._handle_template_not_found(name)

70

71 source = self.mapping[name]

72

73 # For dictionary loader, we use None as filename and create an uptodate function

74 # that checks if the template still exists and has the same content

75 def uptodate() -> bool:

76 # Check if template still exists

77 if name not in self.mapping:

78 return False

79 # Check if content is the same

80 current_content = self.mapping[name]

81 return current_content == source

82

83 source_data = (source, None, uptodate)

84

85 # Cache the result

86 if cache_manager:

87 cache_manager.set("template", cache_key, source_data)

88

89 return source_data

90

91 @internalcode

92 async def list_templates_async(self) -> list[str]:

93 """List all templates in the mapping asynchronously.

94

95 Returns:

96 Sorted list of template names

97 """

98 self._ensure_initialized()

99 return sorted(self.mapping.keys())

100

101 def add_template(self, name: str, source: str) -> None:

102 """Add a new template to the mapping.

103

104 Args:

105 name: Template name

106 source: Template source code

107 """

108 self.mapping[name] = source

109

110 def remove_template(self, name: str) -> None:

111 """Remove a template from the mapping.

112

113 Args:

114 name: Template name to remove

115

116 Raises:

117 KeyError: If template is not in the mapping

118 """

119 del self.mapping[name]

120

121 def update_mapping(self, mapping: dict[str, str]) -> None:

122 """Update the template mapping.

123

124 Args:

125 mapping: New mapping to merge with existing templates

126 """

127 self.mapping.update(mapping)

128

129 def clear_templates(self) -> None:

130 """Clear all templates from the mapping."""

131 self.mapping.clear()

132

133 def has_template(self, name: str) -> bool:

134 """Check if a template exists in the mapping.

135

136 Args:

137 name: Template name to check

138

139 Returns:

140 True if template exists, False otherwise

141 """

142 return name in self.mapping