Coverage for webapp/search/views.py: 81%

84 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-18 22:11 +0000

1import logging 

2from flask import ( 

3 Blueprint, 

4 jsonify, 

5 request, 

6 render_template, 

7) 

8from redis_cache.cache_utility import redis_cache 

9from webapp.config import SEARCH_FIELDS 

10from webapp.search.logic import ( 

11 search_docs, 

12 search_topics, 

13 search_charms, 

14 search_bundles, 

15) 

16from webapp.observability.utils import trace_function 

17from webapp.store_api import publisher_gateway 

18 

19 

20logger = logging.getLogger(__name__) 

21 

22search = Blueprint( 

23 "search", __name__, template_folder="/templates", static_folder="/static" 

24) 

25 

26 

27@trace_function 

28@search.route("/all-search") 

29def all_search(): 

30 return render_template("all-search.html") 

31 

32 

33@trace_function 

34@search.route("/all-search.json") 

35def all_search_json(): 

36 params = request.args 

37 term = params.get("q") 

38 limit = int(params.get("limit", 5)) 

39 

40 if not term: 

41 return {"error": "No search term provided"} 

42 key = ("all-search-json", {"q": term, "limit": limit}) 

43 result = redis_cache.get(key, expected_type=dict) 

44 if result: 

45 return jsonify(result) 

46 result = { 

47 "charms": search_charms(term)[:limit], 

48 "bundles": search_bundles(term)[:limit], 

49 "docs": search_docs(term)[:limit], 

50 "topics": search_topics(term, 1, False)[:limit], 

51 } 

52 redis_cache.set(key, result, ttl=600) 

53 return jsonify(result) 

54 

55 

56@trace_function 

57@search.route("/all-charms") 

58@search.route("/all-bundles") 

59def all_charms() -> dict: 

60 query = request.args.get("q", "") 

61 page = int(request.args.get("page", 1)) 

62 limit = int(request.args.get("limit", 50)) 

63 key = (f"{request.path}", {"q": query, "pg": page}) 

64 cached_page = redis_cache.get(key, expected_type=dict) 

65 if cached_page: 

66 return jsonify(cached_page) 

67 packages = publisher_gateway.find(query, fields=SEARCH_FIELDS) 

68 package_type = request.path[1:-1].split("-")[1] 

69 result = [ 

70 package 

71 for package in packages["results"] 

72 if package["type"] == package_type 

73 ] 

74 start = (page - 1) * limit 

75 end = start + limit 

76 result = {f"{package_type}s": result[start:end]} 

77 redis_cache.set(key, result, ttl=600) 

78 return jsonify(result) 

79 

80 

81@trace_function 

82@search.route("/all-docs") 

83def all_docs(): 

84 search_term = request.args.get("q") 

85 limit = int(request.args.get("limit", 50)) 

86 

87 docs = search_docs(search_term)[:limit] 

88 

89 return jsonify({"docs": docs}) 

90 

91 

92@trace_function 

93@search.route("/all-topics") 

94def all_topics(): 

95 search_term = request.args.get("q") 

96 page = int(request.args.get("page", 1)) 

97 limit = int(request.args.get("limit", 50)) 

98 

99 key = ("all-topics", {"q": search_term, "pg": page}) 

100 topics = redis_cache.get(key, expected_type=dict) 

101 if topics: 

102 return jsonify(topics) 

103 

104 all_topics = search_topics(search_term, page, True)[:limit] 

105 total_pages = -(len(all_topics) // -limit) 

106 start = (page - 1) * limit 

107 end = start + limit 

108 topics = {"topics": all_topics[start:end], "total_pages": total_pages} 

109 redis_cache.set(key, topics, ttl=600) 

110 return jsonify(topics) 

111 

112 

113@search.route("/validate-charm") 

114def validate_charm(): 

115 charm_name = request.args.get("name", "").strip() 

116 

117 if not charm_name: 

118 return jsonify({"exists": False, "error": "No charm name provided"}) 

119 

120 try: 

121 charm = publisher_gateway.get_item_details(charm_name) 

122 

123 if charm and "name" in charm: 

124 return jsonify({"exists": True, "name": charm["name"]}) 

125 

126 return jsonify({"exists": False}) 

127 except Exception as e: 

128 logger.exception(f"Failed to validate charm: {e}") 

129 return jsonify({"exists": False, "error": "Network or API error"})