Coverage for app/backend/src/couchers/servicers/resources.py: 100%
35 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 11:56 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 11:56 +0000
1import functools
2import logging
3from collections import Counter
5import babel
6from google.protobuf import empty_pb2
7from sqlalchemy.orm import Session
9from couchers.context import CouchersContext
10from couchers.i18n.locales import get_babel_locale, get_main_i18next
11from couchers.i18n.localize import try_localize_language_name_from_iso639, try_localize_region_name_from_iso3166
12from couchers.proto import resources_pb2, resources_pb2_grpc
13from couchers.resources import get_badge_dict, get_icon, get_language_dict, get_region_dict, get_terms_of_service
15logger = logging.getLogger(__name__)
17COMMUNITY_GUIDELINES = [
18 {"key": "be_kind", "icon": "hand_with_heart.svg"},
19 {"key": "no_dating", "icon": "ghost.svg"},
20 {"key": "be_safe", "icon": "shield.svg"},
21 {"key": "no_money", "icon": "handshake.svg"},
22]
25# These responses are static per locale, and localizing ~300 names costs tens of ms of CPU (mostly
26# babel.Locale.parse), so cache the built protos. Sharing one message across requests is safe as
27# long as nothing mutates responses after return (the tracing interceptor deepcopies before
28# sanitizing).
29@functools.lru_cache
30def _get_regions_res(locale: babel.Locale) -> resources_pb2.GetRegionsRes:
31 return resources_pb2.GetRegionsRes(
32 regions=[
33 resources_pb2.Region(
34 alpha3=alpha3,
35 name=try_localize_region_name_from_iso3166(alpha3, [locale]) or english_name,
36 )
37 for alpha3, english_name in sorted(get_region_dict().items())
38 ]
39 )
42@functools.lru_cache
43def _get_languages_res(locale_list: tuple[str, ...]) -> resources_pb2.GetLanguagesRes:
44 locale = get_babel_locale(locale_list[0])
45 names_by_code = {
46 code: try_localize_language_name_from_iso639(code, [locale], standalone=True) or english_name
47 for code, english_name in sorted(get_language_dict().items())
48 }
49 name_counts = Counter(names_by_code.values())
50 i18next = get_main_i18next()
51 display_names_by_code = {
52 code: (
53 i18next.localize(
54 "ambiguous_language_display_format",
55 list(locale_list),
56 substitutions={"name": name, "code": code},
57 )
58 if name_counts[name] > 1
59 else name
60 )
61 for code, name in names_by_code.items()
62 }
63 return resources_pb2.GetLanguagesRes(
64 languages=[resources_pb2.Language(code=code, name=name) for code, name in display_names_by_code.items()]
65 )
68class Resources(resources_pb2_grpc.ResourcesServicer):
69 def GetTermsOfService(
70 self, request: empty_pb2.Empty, context: CouchersContext, session: Session
71 ) -> resources_pb2.GetTermsOfServiceRes:
72 return resources_pb2.GetTermsOfServiceRes(terms_of_service=get_terms_of_service())
74 def GetCommunityGuidelines(
75 self, request: empty_pb2.Empty, context: CouchersContext, session: Session
76 ) -> resources_pb2.GetCommunityGuidelinesRes:
77 return resources_pb2.GetCommunityGuidelinesRes(
78 community_guidelines=[
79 resources_pb2.CommunityGuideline(
80 title=context.localization.localize_string(f"community_guidelines.{cg['key']}_title"),
81 guideline=context.localization.localize_string(f"community_guidelines.{cg['key']}_guideline"),
82 icon_svg=get_icon(cg["icon"]),
83 )
84 for cg in COMMUNITY_GUIDELINES
85 ]
86 )
88 def GetRegions(
89 self, request: empty_pb2.Empty, context: CouchersContext, session: Session
90 ) -> resources_pb2.GetRegionsRes:
91 return _get_regions_res(context.localization.preferred_babel_locale)
93 def GetLanguages(
94 self, request: empty_pb2.Empty, context: CouchersContext, session: Session
95 ) -> resources_pb2.GetLanguagesRes:
96 return _get_languages_res(tuple(context.localization.locale_list))
98 def GetBadges(
99 self, request: empty_pb2.Empty, context: CouchersContext, session: Session
100 ) -> resources_pb2.GetBadgesRes:
101 return resources_pb2.GetBadgesRes(
102 badges=[
103 resources_pb2.Badge(
104 id=badge.id,
105 name=context.localization.localize_string(f"badges.{badge.id}_name"),
106 description=context.localization.localize_string(f"badges.{badge.id}_description"),
107 color=badge.color,
108 )
109 for badge in get_badge_dict().values()
110 ]
111 )