From 924f2b96449b2fc68532c4e95dc8ab550dfbb302 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 19 Oct 2026 08:32:09 +0100 Subject: [PATCH] api: fix python code generation (#7764) * api: fix python code generation * fix python types --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- bots/src/API/Docs/Generate/Python.hs | 45 +++++++++---------- .../src/simplex_chat/types/_types.py | 7 +-- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/bots/src/API/Docs/Generate/Python.hs b/bots/src/API/Docs/Generate/Python.hs index 64aa1d1062..8d60bced77 100644 --- a/bots/src/API/Docs/Generate/Python.hs +++ b/bots/src/API/Docs/Generate/Python.hs @@ -60,10 +60,7 @@ commandsCodeText = <> respAliasLine where constrName = T.pack $ fstToUpper tag - classDef = - ("class " <> constrName <> "(TypedDict):\n") - <> bodyOrPass (fieldsCodePy " " "T." params) - <> "\n" + classDef = typedDictCode constrName (fieldsCodePy "T." params) <> "\n" cmdStringFunc = ("\ndef " <> constrName <> "_cmd_string(self: " <> constrName <> ") -> str:\n") <> " return " <> pySelfSyntaxText "T." (fstToUpper tag, params) syntax <> "\n" @@ -143,9 +140,7 @@ typesCodeText = "FileProtocol" -> map toUpper m _ -> m typeDefCode = case typeDef of - ATDRecord fields -> - ("\nclass " <> name' <> "(TypedDict):\n") - <> bodyOrPass (fieldsCodePy " " "" fields) + ATDRecord fields -> "\n" <> typedDictCode name' (fieldsCodePy "" fields) ATDEnum cs -> "\n" <> name' <> " = Literal[" <> T.intercalate ", " (map (\m -> "\"" <> T.pack (enumValue m) <> "\"") $ L.toList cs) @@ -231,9 +226,7 @@ unionTypeCodePy memberName typesNamespace name cs = constrTypeRef (ATUnionMember tag _) = memberName name tag tagLiterals = T.intercalate ", " $ map (\(ATUnionMember tag _) -> "\"" <> T.pack tag <> "\"") $ L.toList cs memberClass (ATUnionMember tag fields) = - ("\nclass " <> memberName name tag <> "(TypedDict):\n") - <> (" type: Literal[\"" <> T.pack tag <> "\"]\n") - <> fieldsCodePy " " typesNamespace fields + "\n" <> typedDictCode (memberName name tag) (("type", "Literal[\"" <> T.pack tag <> "\"]", "") : fieldsCodePy typesNamespace fields) -- | Render the right-hand side of a union alias: either inline (one line) or -- multi-line wrapped in parentheses with `|` separators between alternatives. @@ -248,21 +241,27 @@ unionAliasRhs lhs constr cs (h : t) -> h : map ("| " <>) t cs' = map constr cs --- | Emit a body of `pass` if there are no fields, otherwise the rendered --- fields as-is. -bodyOrPass :: Text -> Text -bodyOrPass body - | T.null body = " pass\n" - | otherwise = body +typedDictCode :: Text -> [(Text, Text, Text)] -> Text +typedDictCode name fields + | any (\(field, _, _) -> field `elem` pyKeywords) fields = + (name <> " = TypedDict(\"" <> name <> "\", {\n") + <> foldMap (\(field, t, comment) -> " \"" <> field <> "\": " <> t <> "," <> comment <> "\n") fields + <> "})\n" + | null fields = "class " <> name <> "(TypedDict):\n pass\n" + | otherwise = + ("class " <> name <> "(TypedDict):\n") + <> foldMap (\(field, t, comment) -> " " <> field <> ": " <> t <> comment <> "\n") fields --- | Render record fields for a TypedDict body. Each field becomes --- `: [ # ]`. Optional fields wrap the type in --- `NotRequired[...]`. -fieldsCodePy :: Text -> Text -> [APIRecordField] -> Text -fieldsCodePy indent namespace = foldMap render +pyKeywords :: [Text] +pyKeywords = + ["False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with", "yield"] + +-- | Render record fields for a TypedDict as name, type and comment. +-- Optional fields wrap the type in `NotRequired[...]`. +fieldsCodePy :: Text -> [APIRecordField] -> [(Text, Text, Text)] +fieldsCodePy namespace = map render where - render (APIRecordField name t) = - indent <> T.pack name <> ": " <> wrapOptional t (typeText t) <> typeComment t <> "\n" + render (APIRecordField name t) = (T.pack name, wrapOptional t (typeText t), typeComment t) wrapOptional t inner = case t of ATOptional _ -> "NotRequired[" <> inner <> "]" _ -> inner diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_types.py b/packages/simplex-chat-python/src/simplex_chat/types/_types.py index 373cf58d43..5457f9c154 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_types.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_types.py @@ -3071,9 +3071,10 @@ class SecurityCode(TypedDict): securityCode: str verifiedAt: str # ISO-8601 timestamp -class SegmentRange(TypedDict): - from: int # int - to: int # int +SegmentRange = TypedDict("SegmentRange", { + "from": int, # int + "to": int, # int +}) class SimplePreference(TypedDict): allow: "FeatureAllowed"