From e85920c23d710b0721bc1955b77427ada73f7cff Mon Sep 17 00:00:00 2001 From: Javier Chulvi Bernad Date: Wed, 23 Sep 2026 14:50:47 +0200 Subject: [PATCH 1/3] List union body key sets in generated Python docstrings --- lib/generators/python.ts | 28 ++++++++++++++++++++++++++-- lib/openapi.ts | 1 + 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.ts b/lib/generators/python.ts index 440f418..abb8add 100644 --- a/lib/generators/python.ts +++ b/lib/generators/python.ts @@ -84,6 +84,11 @@ function literalValues(document: OpenApiDocument, input: JsonSchema, depth = 0): return values.every((value) => value !== undefined) ? values.flatMap((value) => value ?? []) : undefined; } +function objectUnion(document: OpenApiDocument, schema: JsonSchema | undefined): JsonSchema[] | undefined { + const variants = schema?.oneOf ?? schema?.anyOf; + return variants?.length && variants.every((item) => objectSchema(document, item)?.properties) ? variants : undefined; +} + function pythonType(document: OpenApiDocument, input: JsonSchema | undefined, collection = "list"): string { const schema = resolveSchema(document, input); if (!schema) return "Any"; @@ -95,7 +100,7 @@ function pythonType(document: OpenApiDocument, input: JsonSchema | undefined, co if (schema.enum?.length) return result(`Literal[${schema.enum.map(quote).join(", ")}]`); const variants = schema.oneOf ?? schema.anyOf; if (variants?.length) { - if (variants.every((item) => objectSchema(document, item)?.properties)) return result("dict[str, Any]"); + if (objectUnion(document, schema)) return result("dict[str, Any]"); const literalVariants = literalValues(document, schema); if (literalVariants) { const values = [...new Set(literalVariants)]; @@ -198,6 +203,25 @@ function prepare(document: OpenApiDocument): Map { return resources; } +function objectVariants(document: OpenApiDocument, input: JsonSchema | undefined, name: string): string { + const variants = objectUnion(document, resolveSchema(document, input)); + if (!variants) return ""; + const shapes = variants.map((item) => { + const variant = objectSchema(document, item); + const keys = Object.entries(variant?.properties ?? {}) + .filter(([, property]) => { + const forbidden = resolveSchema(document, property)?.not; + return !forbidden || Object.keys(forbidden).length > 0; // `not: {}` forbids the key in this variant + }) + .map(([key, property]) => { + const values = literalValues(document, property); + return `${key}${variant?.required?.includes(key) ? "" : "?"}${values ? `: ${values.map((value) => JSON.stringify(value)).join("|")}` : ""}`; + }); + return `{${keys.join(", ")}}`; + }); + return `\n ${pythonDocstringText(`Valid ${name} objects (? marks an optional key): ${shapes.join(" or ")}`)}`; +} + function docstring(document: OpenApiDocument, operation: PythonOperation, returnType: string): string { const lines = [ ` """${pythonDocstringText((operation.summary ?? operation.name).replace(/\.$/, ""), " ")}.`, @@ -206,7 +230,7 @@ function docstring(document: OpenApiDocument, operation: PythonOperation, return lines.push("", " Args:"); for (const argument of operation.arguments) { lines.push( - ` ${argument.pythonName} (${pythonType(document, argument.schema, "Sequence")}${argument.required ? "" : ", optional"}): ${pythonDocstringText(argument.description, " ")}`, + ` ${argument.pythonName} (${pythonType(document, argument.schema, "Sequence")}${argument.required ? "" : ", optional"}): ${pythonDocstringText(argument.description, " ")}${objectVariants(document, argument.schema, argument.pythonName)}`, ); } lines.push( diff --git a/lib/openapi.ts b/lib/openapi.ts index 4f426ce..badf3ff 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -25,6 +25,7 @@ export interface JsonSchema { minProperties?: number; minimum?: number; multipleOf?: number; + not?: JsonSchema; nullable?: boolean; oneOf?: JsonSchema[]; prefixItems?: JsonSchema[]; From a6b91ad1a800bd18ae5ecdc310de96411ec97fb9 Mon Sep 17 00:00:00 2001 From: Javier Chulvi Bernad Date: Wed, 23 Sep 2026 16:18:27 +0200 Subject: [PATCH 2/3] Resolve boolean and referenced not subschemas before hiding keys --- lib/generators/python.ts | 7 +++++-- lib/openapi.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.ts b/lib/generators/python.ts index abb8add..02ec4de 100644 --- a/lib/generators/python.ts +++ b/lib/generators/python.ts @@ -210,8 +210,11 @@ function objectVariants(document: OpenApiDocument, input: JsonSchema | undefined const variant = objectSchema(document, item); const keys = Object.entries(variant?.properties ?? {}) .filter(([, property]) => { - const forbidden = resolveSchema(document, property)?.not; - return !forbidden || Object.keys(forbidden).length > 0; // `not: {}` forbids the key in this variant + const negated = resolveSchema(document, property)?.not; + const matchesAll = + negated === true || + (typeof negated === "object" && !Object.keys(resolveSchema(document, negated) ?? {}).length); + return !matchesAll; // `not: true` or `not: {}` forbids the key in this variant }) .map(([key, property]) => { const values = literalValues(document, property); diff --git a/lib/openapi.ts b/lib/openapi.ts index badf3ff..7aacada 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -25,7 +25,7 @@ export interface JsonSchema { minProperties?: number; minimum?: number; multipleOf?: number; - not?: JsonSchema; + not?: boolean | JsonSchema; nullable?: boolean; oneOf?: JsonSchema[]; prefixItems?: JsonSchema[]; From 5f13eabd665f40156985b1a30fae0604b8dc766c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 24 Sep 2026 10:29:00 +0100 Subject: [PATCH 3/3] Preserve shared object constraints in union help --- lib/generators/python.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.ts b/lib/generators/python.ts index 02ec4de..6e4f211 100644 --- a/lib/generators/python.ts +++ b/lib/generators/python.ts @@ -204,10 +204,11 @@ function prepare(document: OpenApiDocument): Map { } function objectVariants(document: OpenApiDocument, input: JsonSchema | undefined, name: string): string { - const variants = objectUnion(document, resolveSchema(document, input)); + const schema = resolveSchema(document, input); + const variants = objectUnion(document, schema); if (!variants) return ""; const shapes = variants.map((item) => { - const variant = objectSchema(document, item); + const variant = objectSchema(document, { allOf: [{ ...schema, oneOf: undefined, anyOf: undefined }, item] }); const keys = Object.entries(variant?.properties ?? {}) .filter(([, property]) => { const negated = resolveSchema(document, property)?.not;