Coverage for src/couchers/resources.py: 94%

69 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-11-21 04:21 +0000

1import functools 

2import json 

3import logging 

4from pathlib import Path 

5 

6from sqlalchemy.sql import delete, text 

7 

8from couchers.config import config 

9from couchers.db import session_scope 

10from couchers.models import Language, Region, TimezoneArea 

11from couchers.sql import couchers_select as select 

12from proto import resources_pb2 

13 

14logger = logging.getLogger(__name__) 

15 

16resources_folder = Path(__file__).parent / ".." / ".." / "resources" 

17 

18 

19@functools.cache 

20def get_terms_of_service(): 

21 """ 

22 Get the latest terms of service 

23 """ 

24 with open(resources_folder / "terms_of_service.md", "r") as f: 

25 return f.read() 

26 

27 

28@functools.cache 

29def get_community_guidelines(): 

30 """ 

31 Get the latest Community Guidelines 

32 """ 

33 with open(resources_folder / "community_guidelines.json", "r") as f: 

34 community_guidelines = json.load(f) 

35 ret = [] 

36 for cg in community_guidelines: 

37 with open(resources_folder / "icons" / cg["icon"], "r") as f: 

38 ret.append( 

39 resources_pb2.CommunityGuideline( 

40 title=cg["title"], 

41 guideline=cg["guideline"], 

42 icon_svg=f.read(), 

43 ) 

44 ) 

45 return ret 

46 

47 

48@functools.cache 

49def get_region_dict(): 

50 """ 

51 Get list of allowed regions as a dictionary of {alpha3: name}. 

52 """ 

53 with session_scope() as session: 

54 return {region.code: region.name for region in session.execute(select(Region)).scalars().all()} 

55 

56 

57def region_is_allowed(code): 

58 """ 

59 Check a region code is valid 

60 """ 

61 return code in get_region_dict() 

62 

63 

64@functools.cache 

65def get_language_dict(): 

66 """ 

67 Get list of allowed languages as a dictionary of {code: name}. 

68 """ 

69 with session_scope() as session: 

70 return {language.code: language.name for language in session.execute(select(Language)).scalars().all()} 

71 

72 

73@functools.cache 

74def get_badge_data(): 

75 """ 

76 Get list of profile badges in form {id: Badge} 

77 """ 

78 with open(resources_folder / "badges.json", "r") as f: 

79 return json.load(f) 

80 

81 

82@functools.cache 

83def get_badge_dict(): 

84 """ 

85 Get list of profile badges in form {id: Badge} 

86 """ 

87 return {badge["id"]: badge for badge in get_badge_data()["badges"]} 

88 

89 

90@functools.cache 

91def get_static_badge_dict(): 

92 """ 

93 Get list of static badges in form {id: list(user_ids)} 

94 """ 

95 return get_badge_data()["static_badges"] 

96 

97 

98def language_is_allowed(code): 

99 """ 

100 Check a language code is valid 

101 """ 

102 return code in get_language_dict() 

103 

104 

105def copy_resources_to_database(session): 

106 """ 

107 Syncs the source-of-truth data from files into the database. Call this at the end of a migration. 

108 

109 Foreign key constraints that refer to resource tables need to be set to DEFERRABLE. 

110 

111 We sync as follows: 

112 

113 1. Lock the table to be updated fully 

114 2. Defer all constraints 

115 3. Truncate the table 

116 4. Re-insert everything 

117 

118 Truncating and recreating guarantees the data is fully in sync. 

119 """ 

120 with open(resources_folder / "regions.json", "r") as f: 

121 regions = [(region["alpha3"], region["name"]) for region in json.load(f)] 

122 

123 with open(resources_folder / "languages.json", "r") as f: 

124 languages = [(language["code"], language["name"]) for language in json.load(f)] 

125 

126 timezone_areas_file = resources_folder / "timezone_areas.sql" 

127 

128 if not timezone_areas_file.exists(): 

129 if not config["DEV"]: 

130 raise Exception("Missing timezone_areas.sql and not running in dev") 

131 

132 timezone_areas_file = resources_folder / "timezone_areas.sql-fake" 

133 logger.info("Using fake timezone areas") 

134 

135 with open(timezone_areas_file, "r") as f: 

136 tz_sql = f.read() 

137 

138 # set all constraints marked as DEFERRABLE to be checked at the end of this transaction, not immediately 

139 session.execute(text("SET CONSTRAINTS ALL DEFERRED")) 

140 

141 session.execute(delete(Region)) 

142 for code, name in regions: 

143 session.add(Region(code=code, name=name)) 

144 

145 session.execute(delete(Language)) 

146 for code, name in languages: 

147 session.add(Language(code=code, name=name)) 

148 

149 session.execute(delete(TimezoneArea)) 

150 session.execute(text(tz_sql))