Coverage for app / services / category_service.py: 100%

45 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-06 04:49 +0000

1""" 

2This module provides tools for managing book categories and their hierarchy. 

3 

4It includes functionality to encode and decode category IDs, construct category 

5trees, and fetch unique categories from the database. The module is designed for 

6building Bootstrap-compatible tree structures for UI representations. 

7""" 

8import base64 

9 

10from app import db 

11from app.models import Book 

12 

13 

14def get_category_bs_tree(): 

15 """ 

16 Constructs and returns a Bootstrap-formatted tree representation of categories. 

17 

18 The function retrieves a hierarchical category tree and processes it into a 

19 format suitable for a Bootstrap UI component. Each item in the resulting tree 

20 contains metadata such as the category name, a unique id based on its full path, 

21 and a checked state. 

22 

23 :raises ValueError: If the category tree data is malformed. 

24 

25 :return: A list of dictionaries representing the category tree in a structure 

26 compatible with Bootstrap frameworks. 

27 :rtype: list[dict] 

28 """ 

29 def _add_categories(cat, context, tree, children): 

30 """ 

31 Adds a category and its subcategories to the tree representation. 

32 

33 The function recursively creates a tree structure, where each node represents 

34 a category. Each category node is stored as a dictionary with information about  

35 the category's text, state (e.g., checked or unchecked), the full path to the  

36 category, and an identifier. If the category has children, they are also added  

37 to the tree recursively. 

38 

39 :param cat: The current category name being added. 

40 :type cat: str 

41 :param context: The current hierarchical path leading to the category. 

42 :type context: str 

43 :param tree: A list representing the current level of the tree structure. 

44 :type tree: list 

45 :param children: A dictionary of child categories, where the keys are child  

46 category names, and the values are their respective sub-children. 

47 :type children: dict 

48 :return: None 

49 """ 

50 fullpath = context + _SEPARATOR + cat if context else cat 

51 node = { 

52 "text": cat, 

53 "state": {"checked": False}, 

54 "fullpath": fullpath, 

55 "id": _fullpath_to_id(fullpath) 

56 } 

57 tree.append(node) 

58 if children: 

59 node["nodes"] = [] 

60 for child in children: 

61 _add_categories(child, fullpath, node["nodes"], children[child]) 

62 

63 categories = _get_category_tree() 

64 bs_tree = [] 

65 for category, subcategories in categories.items(): 

66 _add_categories(category, '', bs_tree, subcategories) 

67 return bs_tree 

68 

69 

70def id_to_fullpath(encoded_id): 

71 """ 

72 Decodes a URL-safe HTML id string back into the original fullpath using Base64. 

73 

74 The function reverses the transformations applied in `fullpath_to_id`, ensuring 

75 that the output matches the original input string. 

76 

77 :param encoded_id: The Base64-encoded URL-safe HTML id string to be decoded. 

78 :type encoded_id: str 

79 :return: The original fullpath string. 

80 :rtype: str 

81 """ 

82 safe_decoded = (encoded_id 

83 .replace('-', '+') 

84 .replace('_', '/') 

85 .replace('*', '=')) 

86 decoded = base64.b64decode(safe_decoded).decode('utf-8') 

87 return decoded 

88 

89 

90def _get_category_list(): 

91 """ 

92 Fetches a list of unique book categories from the database sorted in alphabetical 

93 order. 

94 

95 The function establishes a database connection and executes an SQL query to 

96 retrieve distinct book categories from the database. After 

97 retrieving the results, the function processes and returns the list of unique 

98 categories. 

99 

100 :raises sqlalchemy.exc.SQLAlchemyError: If there is an error executing the 

101 SQL query or an issue with the database connection. 

102 

103 :return: A list of strings containing distinct book categories sorted 

104 alphabetically. 

105 :rtype: list[str] 

106 """ 

107 result = (db.session.query(Book.categories_flat) 

108 .distinct() 

109 .order_by(Book.categories_flat) 

110 .all()) 

111 

112 # Extracting the results into a list 

113 categories = [row.categories_flat for row in result] 

114 return categories 

115 

116 

117_SEPARATOR = ' > ' # separator for full category strings 

118 

119 

120def _get_category_tree(): 

121 """ 

122 Builds a hierarchical tree of categories from a flat list of category strings 

123 separated by ' > '. Each category string represents a path, where top-level 

124 categories are separated by '>' from their respective subcategories. 

125 

126 The function `get_category_tree` iterates through a list of categories to 

127 construct a nested dictionary structure representing the hierarchical 

128 relationship among categories. An auxiliary recursive function `_add_categories` 

129 is used to process each category path and build the tree. 

130 

131 :raises None: This function does not raise any errors. 

132 

133 :return: A dictionary where keys are category names and values are nested 

134 dictionaries representing subcategories. 

135 :rtype: dict 

136 """ 

137 def _add_categories(cat, tree, sub_categories): 

138 if cat not in tree: 

139 tree[cat] = {} 

140 if sub_categories: 

141 # remove first subcategory and make sub_categories list smaller 

142 sub_category = sub_categories.pop(0) 

143 _add_categories(sub_category, tree[cat], sub_categories) 

144 

145 categories = _get_category_list() 

146 category_tree = {} 

147 for category in categories: 

148 categories = category.split(_SEPARATOR) 

149 top_level_category = categories.pop(0) 

150 _add_categories(top_level_category, category_tree, categories) 

151 return category_tree 

152 

153 

154def _fullpath_to_id(fullpath): 

155 """ 

156 Converts a fullpath string into a URL-safe HTML id by encoding it using Base64. 

157 

158 The transformation encodes the input as a Base64 string, replaces URL-sensitive 

159 characters, and ensures that the resulting id strings are unique and reversible. 

160 

161 :param fullpath: The original fullpath string to be converted. 

162 :type fullpath: str 

163 :return: A URL-safe Base64-encoded HTML id string. 

164 :rtype: str 

165 """ 

166 encoded = base64.b64encode(fullpath.encode('utf-8')).decode('utf-8') 

167 safe_encoded = (encoded 

168 .replace('+', '-') 

169 .replace('/', '_') 

170 .replace('=', '*')) 

171 return safe_encoded 

172 

173 

174__all__ = ['get_category_bs_tree', 'id_to_fullpath']