fix: sync locales in user registration validation (#3278)

* Add ability to inject into Python files

* Update outdated references to gen_global_components.py

* Add code gen for registration locale validation

* sort validators

* update for pydantic 2

* run generator again

---------

Co-authored-by: Gasper Gril <gasper@gril.si>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
Hayden 2024-03-10 12:58:52 -05:00 committed by GitHub
parent 02da2114f9
commit b54cdf6425
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 108 additions and 88 deletions

View File

@ -3,8 +3,8 @@ from pathlib import Path
from fastapi import FastAPI from fastapi import FastAPI
from jinja2 import Template from jinja2 import Template
from pydantic import BaseModel from pydantic import BaseModel, ConfigDict
from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject, RequestType
CWD = Path(__file__).parent CWD = Path(__file__).parent
@ -12,23 +12,25 @@ OUTFILE = PROJECT_DIR / "tests" / "utils" / "api_routes" / "__init__.py"
class PathObject(BaseModel): class PathObject(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
route_object: RouteObject route_object: RouteObject
http_verbs: list[HTTPRequest] http_verbs: list[HTTPRequest]
class Config:
arbitrary_types_allowed = True
def get_path_objects(app: FastAPI): def get_path_objects(app: FastAPI):
paths = [] paths = []
for key, value in app.openapi().items(): for key, value in app.openapi().items():
if key == "paths": if key == "paths":
for key, value in value.items(): for key, value2 in value.items():
verbs = []
for k, v in value2.items():
verbs.append(HTTPRequest(request_type=k, **v))
paths.append( paths.append(
PathObject( PathObject(
route_object=RouteObject(key), route_object=RouteObject(key),
http_verbs=[HTTPRequest(request_type=k, **v) for k, v in value.items()], http_verbs=verbs,
) )
) )

View File

@ -5,7 +5,7 @@ from pathlib import Path
import dotenv import dotenv
import requests import requests
from jinja2 import Template from jinja2 import Template
from pydantic import Extra from pydantic import ConfigDict
from requests import Response from requests import Response
from utils import CodeDest, CodeKeys, inject_inline, log from utils import CodeDest, CodeKeys, inject_inline, log
@ -56,7 +56,7 @@ LOCALE_DATA: dict[str, LocaleData] = {
"zh-TW": LocaleData(name="繁體中文 (Chinese traditional)"), "zh-TW": LocaleData(name="繁體中文 (Chinese traditional)"),
} }
LOCALE_TEMPLATE = """// This Code is auto generated by gen_global_components.py LOCALE_TEMPLATE = """// This Code is auto generated by gen_ts_locales.py
export const LOCALES = [{% for locale in locales %} export const LOCALES = [{% for locale in locales %}
{ {
name: "{{ locale.name }}", name: "{{ locale.name }}",
@ -70,6 +70,8 @@ export const LOCALES = [{% for locale in locales %}
class TargetLanguage(MealieModel): class TargetLanguage(MealieModel):
model_config = ConfigDict(populate_by_name=True, extra="allow")
id: str id: str
name: str name: str
locale: str locale: str
@ -78,10 +80,6 @@ class TargetLanguage(MealieModel):
twoLettersCode: str twoLettersCode: str
progress: float = 0.0 progress: float = 0.0
class Config:
extra = Extra.allow
allow_population_by_field_name = True
class CrowdinApi: class CrowdinApi:
project_name = "Mealie" project_name = "Mealie"
@ -152,6 +150,7 @@ PROJECT_DIR = Path(__file__).parent.parent.parent
datetime_dir = PROJECT_DIR / "frontend" / "lang" / "dateTimeFormats" datetime_dir = PROJECT_DIR / "frontend" / "lang" / "dateTimeFormats"
locales_dir = PROJECT_DIR / "frontend" / "lang" / "messages" locales_dir = PROJECT_DIR / "frontend" / "lang" / "messages"
nuxt_config = PROJECT_DIR / "frontend" / "nuxt.config.js" nuxt_config = PROJECT_DIR / "frontend" / "nuxt.config.js"
reg_valid = PROJECT_DIR / "mealie" / "schema" / "_mealie" / "validators.py"
""" """
This snippet walks the message and dat locales directories and generates the import information This snippet walks the message and dat locales directories and generates the import information
@ -175,6 +174,19 @@ def inject_nuxt_values():
inject_inline(nuxt_config, CodeKeys.nuxt_local_dates, all_date_locales) inject_inline(nuxt_config, CodeKeys.nuxt_local_dates, all_date_locales)
def inject_registration_validation_values():
all_langs = []
for match in locales_dir.glob("*.json"):
lang_string = f'"{match.stem}",'
all_langs.append(lang_string)
# sort
all_langs.sort()
log.debug(f"injecting locales into user registration validation -> {reg_valid}")
inject_inline(reg_valid, CodeKeys.nuxt_local_messages, all_langs)
def generate_locales_ts_file(): def generate_locales_ts_file():
api = CrowdinApi("") api = CrowdinApi("")
models = api.get_languages() models = api.get_languages()
@ -193,6 +205,7 @@ def main():
generate_locales_ts_file() generate_locales_ts_file()
inject_nuxt_values() inject_nuxt_values()
inject_registration_validation_values()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -6,7 +6,7 @@ from utils import log
# ============================================================ # ============================================================
template = """// This Code is auto generated by gen_global_components.py template = """// This Code is auto generated by gen_ts_types.py
{% for name in global %}import {{ name }} from "@/components/global/{{ name }}.vue"; {% for name in global %}import {{ name }} from "@/components/global/{{ name }}.vue";
{% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue"; {% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue";
{% endfor %} {% endfor %}

View File

@ -1,9 +1,8 @@
import re import re
from enum import Enum from enum import Enum
from typing import Optional
from humps import camelize from humps import camelize
from pydantic import BaseModel, Extra, Field from pydantic import BaseModel, ConfigDict, Field
from slugify import slugify from slugify import slugify
@ -34,33 +33,30 @@ class ParameterIn(str, Enum):
class RouterParameter(BaseModel): class RouterParameter(BaseModel):
model_config = ConfigDict(extra="allow")
required: bool = False required: bool = False
name: str name: str
location: ParameterIn = Field(..., alias="in") location: ParameterIn = Field(..., alias="in")
class Config:
extra = Extra.allow
class RequestBody(BaseModel): class RequestBody(BaseModel):
required: bool = False model_config = ConfigDict(extra="allow")
class Config: required: bool = False
extra = Extra.allow
class HTTPRequest(BaseModel): class HTTPRequest(BaseModel):
model_config = ConfigDict(extra="allow", populate_by_name=True)
request_type: RequestType request_type: RequestType
description: str = "" description: str = ""
summary: str summary: str
requestBody: Optional[RequestBody] request_body: RequestBody | None = None
parameters: list[RouterParameter] = [] parameters: list[RouterParameter] = []
tags: list[str] | None = [] tags: list[str] | None = []
class Config:
extra = Extra.allow
def list_as_js_object_string(self, parameters, braces=True): def list_as_js_object_string(self, parameters, braces=True):
if len(parameters) == 0: if len(parameters) == 0:
return "" return ""
@ -71,11 +67,11 @@ class HTTPRequest(BaseModel):
return ", ".join(parameters) return ", ".join(parameters)
def payload(self): def payload(self):
return "payload" if self.requestBody else "" return "payload" if self.request_body else ""
def function_args(self): def function_args(self):
all_params = [p.name for p in self.parameters] all_params = [p.name for p in self.parameters]
if self.requestBody: if self.request_body:
all_params.append("payload") all_params.append("payload")
return self.list_as_js_object_string(all_params) return self.list_as_js_object_string(all_params)

View File

@ -50,7 +50,7 @@ class CodeSlicer:
self._next_line += 1 self._next_line += 1
def get_indentation_of_string(line: str, comment_char: str = "//") -> str: def get_indentation_of_string(line: str, comment_char: str = "//|#") -> str:
return re.sub(rf"{comment_char}.*", "", line).removesuffix("\n") return re.sub(rf"{comment_char}.*", "", line).removesuffix("\n")

View File

@ -1,9 +1,9 @@
// This Code is auto generated by gen_global_components.py // This Code is auto generated by gen_ts_locales.py
export const LOCALES = [ export const LOCALES = [
{ {
name: "繁體中文 (Chinese traditional)", name: "繁體中文 (Chinese traditional)",
value: "zh-TW", value: "zh-TW",
progress: 30, progress: 29,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -15,7 +15,7 @@ export const LOCALES = [
{ {
name: "Tiếng Việt (Vietnamese)", name: "Tiếng Việt (Vietnamese)",
value: "vi-VN", value: "vi-VN",
progress: 1, progress: 0,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -27,43 +27,43 @@ export const LOCALES = [
{ {
name: "Türkçe (Turkish)", name: "Türkçe (Turkish)",
value: "tr-TR", value: "tr-TR",
progress: 53, progress: 62,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Svenska (Swedish)", name: "Svenska (Swedish)",
value: "sv-SE", value: "sv-SE",
progress: 94, progress: 99,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "српски (Serbian)", name: "српски (Serbian)",
value: "sr-SP", value: "sr-SP",
progress: 32, progress: 31,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Slovenian", name: "Slovenian",
value: "sl-SI", value: "sl-SI",
progress: 47, progress: 49,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Slovak", name: "Slovak",
value: "sk-SK", value: "sk-SK",
progress: 93, progress: 91,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Pусский (Russian)", name: "Pусский (Russian)",
value: "ru-RU", value: "ru-RU",
progress: 98, progress: 99,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Română (Romanian)", name: "Română (Romanian)",
value: "ro-RO", value: "ro-RO",
progress: 42, progress: 44,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -75,19 +75,19 @@ export const LOCALES = [
{ {
name: "Português do Brasil (Brazilian Portuguese)", name: "Português do Brasil (Brazilian Portuguese)",
value: "pt-BR", value: "pt-BR",
progress: 97, progress: 95,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Polski (Polish)", name: "Polski (Polish)",
value: "pl-PL", value: "pl-PL",
progress: 98, progress: 100,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Norsk (Norwegian)", name: "Norsk (Norwegian)",
value: "no-NO", value: "no-NO",
progress: 99, progress: 97,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -99,25 +99,25 @@ export const LOCALES = [
{ {
name: "Latvian", name: "Latvian",
value: "lv-LV", value: "lv-LV",
progress: 1, progress: 0,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Lithuanian", name: "Lithuanian",
value: "lt-LT", value: "lt-LT",
progress: 93, progress: 91,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "한국어 (Korean)", name: "한국어 (Korean)",
value: "ko-KR", value: "ko-KR",
progress: 5, progress: 3,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "日本語 (Japanese)", name: "日本語 (Japanese)",
value: "ja-JP", value: "ja-JP",
progress: 12, progress: 11,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -135,25 +135,25 @@ export const LOCALES = [
{ {
name: "Magyar (Hungarian)", name: "Magyar (Hungarian)",
value: "hu-HU", value: "hu-HU",
progress: 100, progress: 98,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Croatian", name: "Croatian",
value: "hr-HR", value: "hr-HR",
progress: 93, progress: 91,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "עברית (Hebrew)", name: "עברית (Hebrew)",
value: "he-IL", value: "he-IL",
progress: 97, progress: 98,
dir: "rtl", dir: "rtl",
}, },
{ {
name: "Galician", name: "Galician",
value: "gl-ES", value: "gl-ES",
progress: 1, progress: 3,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -165,19 +165,19 @@ export const LOCALES = [
{ {
name: "French, Canada", name: "French, Canada",
value: "fr-CA", value: "fr-CA",
progress: 97, progress: 95,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Suomi (Finnish)", name: "Suomi (Finnish)",
value: "fi-FI", value: "fi-FI",
progress: 91, progress: 89,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Español (Spanish)", name: "Español (Spanish)",
value: "es-ES", value: "es-ES",
progress: 79, progress: 93,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -189,13 +189,13 @@ export const LOCALES = [
{ {
name: "British English", name: "British English",
value: "en-GB", value: "en-GB",
progress: 3, progress: 2,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Ελληνικά (Greek)", name: "Ελληνικά (Greek)",
value: "el-GR", value: "el-GR",
progress: 34, progress: 33,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -219,7 +219,7 @@ export const LOCALES = [
{ {
name: "Català (Catalan)", name: "Català (Catalan)",
value: "ca-ES", value: "ca-ES",
progress: 75, progress: 74,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -231,13 +231,13 @@ export const LOCALES = [
{ {
name: "العربية (Arabic)", name: "العربية (Arabic)",
value: "ar-SA", value: "ar-SA",
progress: 20, progress: 18,
dir: "rtl", dir: "rtl",
}, },
{ {
name: "Afrikaans (Afrikaans)", name: "Afrikaans (Afrikaans)",
value: "af-ZA", value: "af-ZA",
progress: 92, progress: 90,
dir: "ltr", dir: "ltr",
}, },
] ]

View File

@ -1,4 +1,4 @@
// This Code is auto generated by gen_global_components.py // This Code is auto generated by gen_ts_types.py
import AdvancedOnly from "@/components/global/AdvancedOnly.vue"; import AdvancedOnly from "@/components/global/AdvancedOnly.vue";
import AppButtonCopy from "@/components/global/AppButtonCopy.vue"; import AppButtonCopy from "@/components/global/AppButtonCopy.vue";
import AppButtonUpload from "@/components/global/AppButtonUpload.vue"; import AppButtonUpload from "@/components/global/AppButtonUpload.vue";

View File

@ -1,38 +1,47 @@
def validate_locale(locale: str) -> bool: def validate_locale(locale: str) -> bool:
valid = { valid = {
"el-GR", # CODE_GEN_ID: MESSAGE_LOCALES
"it-IT",
"ko-KR",
"es-ES",
"ja-JP",
"zh-CN",
"tr-TR",
"ar-SA",
"hu-HU",
"pt-PT",
"no-NO",
"sv-SE",
"ro-RO",
"sk-SK",
"uk-UA",
"fr-CA",
"pl-PL",
"da-DK",
"pt-BR",
"de-DE",
"ca-ES",
"sr-SP",
"cs-CZ",
"fr-FR",
"zh-TW",
"af-ZA", "af-ZA",
"ru-RU", "ar-SA",
"he-IL", "bg-BG",
"nl-NL", "ca-ES",
"en-US", "cs-CZ",
"da-DK",
"de-DE",
"el-GR",
"en-GB", "en-GB",
"en-US",
"es-ES",
"fi-FI", "fi-FI",
"fr-CA",
"fr-FR",
"gl-ES",
"he-IL",
"hr-HR",
"hu-HU",
"is-IS",
"it-IT",
"ja-JP",
"ko-KR",
"lt-LT",
"lv-LV",
"nl-NL",
"no-NO",
"pl-PL",
"pt-BR",
"pt-PT",
"ro-RO",
"ru-RU",
"sk-SK",
"sl-SI",
"sr-SP",
"sv-SE",
"tr-TR",
"uk-UA",
"vi-VN", "vi-VN",
"zh-CN",
"zh-TW",
# END: MESSAGE_LOCALES
} }
return locale in valid return locale in valid