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 a default path since searchpath cannot be empty

34 # For DictLoader, we use a virtual path since templates are in memory

35 if searchpath is not None:

36 super().__init__(searchpath)

37 else:

38 # Use a virtual path for dictionary loader since no actual filesystem path is needed

39 super().__init__(["/virtual_dict_path"])

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

41

42 @internalcode

43 async def get_source_async(

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

45 ) -> SourceType:

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

47

48 Args:

49 environment: The async environment instance

50 name: Template name to load

51

52 Returns:

53 Tuple of (source, filename, uptodate_func)

54

55 Raises:

56 TemplateNotFound: If template is not in the mapping

57 """

58 self._ensure_initialized()

59

60 # Try to get from cache first

61 cache_manager = self._get_cache_manager(environment)

62 cache_key = f"dict:{id(self)}:{name}"

63

64 if cache_manager:

65 cached_source: SourceType | None = cache_manager.get("template", cache_key)

66 if cached_source is not None:

67 return cached_source

68

69 if name not in self.mapping:

70 self._handle_template_not_found(name)

71

72 source = self.mapping[name]

73

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

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

76 def uptodate() -> bool:

77 # Check if template still exists

78 if name not in self.mapping:

79 return False

80 # Check if content is the same

81 current_content = self.mapping[name]

82 return current_content == source

83

84 source_data: SourceType = (source, None, uptodate)

85

86 # Cache the result

87 if cache_manager:

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

89

90 return source_data

91

92 @internalcode

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

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

95

96 Returns:

97 Sorted list of template names

98 """

99 self._ensure_initialized()

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

101

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

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

104

105 Args:

106 name: Template name

107 source: Template source code

108 """

109 self.mapping[name] = source

110

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

112 """Remove a template from the mapping.

113

114 Args:

115 name: Template name to remove

116

117 Raises:

118 KeyError: If template is not in the mapping

119 """

120 del self.mapping[name]

121

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

123 """Update the template mapping.

124

125 Args:

126 mapping: New mapping to merge with existing templates

127 """

128 self.mapping.update(mapping)

129

130 def clear_templates(self) -> None:

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

132 self.mapping.clear()

133

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

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

136

137 Args:

138 name: Template name to check

139

140 Returns:

141 True if template exists, False otherwise

142 """

143 return name in self.mapping