Coverage for app/backend/src/couchers/servicers/resources.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-23 04:10 +0000

1import functools 

2import logging 

3 

4import babel 

5from google.protobuf import empty_pb2 

6from sqlalchemy.orm import Session 

7 

8from couchers.context import CouchersContext 

9from couchers.i18n.localize import try_localize_language_name_from_iso639, try_localize_region_name_from_iso3166 

10from couchers.proto import resources_pb2, resources_pb2_grpc 

11from couchers.resources import get_badge_dict, get_icon, get_language_dict, get_region_dict, get_terms_of_service 

12 

13logger = logging.getLogger(__name__) 

14 

15COMMUNITY_GUIDELINES = [ 

16 {"key": "be_kind", "icon": "hand_with_heart.svg"}, 

17 {"key": "no_dating", "icon": "ghost.svg"}, 

18 {"key": "be_safe", "icon": "shield.svg"}, 

19 {"key": "no_money", "icon": "handshake.svg"}, 

20] 

21 

22 

23# These responses are static per locale, and localizing ~300 names costs tens of ms of CPU (mostly 

24# babel.Locale.parse), so cache the built protos. Sharing one message across requests is safe as 

25# long as nothing mutates responses after return (the tracing interceptor deepcopies before 

26# sanitizing). 

27@functools.lru_cache 

28def _get_regions_res(locale: babel.Locale) -> resources_pb2.GetRegionsRes: 

29 return resources_pb2.GetRegionsRes( 

30 regions=[ 

31 resources_pb2.Region( 

32 alpha3=alpha3, 

33 name=try_localize_region_name_from_iso3166(alpha3, locale) or english_name, 

34 ) 

35 for alpha3, english_name in sorted(get_region_dict().items()) 

36 ] 

37 ) 

38 

39 

40@functools.lru_cache 

41def _get_languages_res(locale: babel.Locale) -> resources_pb2.GetLanguagesRes: 

42 return resources_pb2.GetLanguagesRes( 

43 languages=[ 

44 resources_pb2.Language( 

45 code=code, 

46 name=try_localize_language_name_from_iso639(code, locale, standalone=True) or english_name, 

47 ) 

48 for code, english_name in sorted(get_language_dict().items()) 

49 ] 

50 ) 

51 

52 

53class Resources(resources_pb2_grpc.ResourcesServicer): 

54 def GetTermsOfService( 

55 self, request: empty_pb2.Empty, context: CouchersContext, session: Session 

56 ) -> resources_pb2.GetTermsOfServiceRes: 

57 return resources_pb2.GetTermsOfServiceRes(terms_of_service=get_terms_of_service()) 

58 

59 def GetCommunityGuidelines( 

60 self, request: empty_pb2.Empty, context: CouchersContext, session: Session 

61 ) -> resources_pb2.GetCommunityGuidelinesRes: 

62 return resources_pb2.GetCommunityGuidelinesRes( 

63 community_guidelines=[ 

64 resources_pb2.CommunityGuideline( 

65 title=context.localization.localize_string(f"community_guidelines.{cg['key']}_title"), 

66 guideline=context.localization.localize_string(f"community_guidelines.{cg['key']}_guideline"), 

67 icon_svg=get_icon(cg["icon"]), 

68 ) 

69 for cg in COMMUNITY_GUIDELINES 

70 ] 

71 ) 

72 

73 def GetRegions( 

74 self, request: empty_pb2.Empty, context: CouchersContext, session: Session 

75 ) -> resources_pb2.GetRegionsRes: 

76 return _get_regions_res(context.localization.babel_locale) 

77 

78 def GetLanguages( 

79 self, request: empty_pb2.Empty, context: CouchersContext, session: Session 

80 ) -> resources_pb2.GetLanguagesRes: 

81 return _get_languages_res(context.localization.babel_locale) 

82 

83 def GetBadges( 

84 self, request: empty_pb2.Empty, context: CouchersContext, session: Session 

85 ) -> resources_pb2.GetBadgesRes: 

86 return resources_pb2.GetBadgesRes( 

87 badges=[ 

88 resources_pb2.Badge( 

89 id=badge.id, 

90 name=context.localization.localize_string(f"badges.{badge.id}_name"), 

91 description=context.localization.localize_string(f"badges.{badge.id}_description"), 

92 color=badge.color, 

93 ) 

94 for badge in get_badge_dict().values() 

95 ] 

96 )