From 725d13bac9e68114ec2fd56abee539585e3f6eb6 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 09:02:03 +0200 Subject: [PATCH 1/2] Use authored request examples in Python samples --- lib/generators/python.test.ts | 38 +++++++++++++++++++++++++++++++++-- lib/openapi.ts | 34 +++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2af7892..a0a65ce 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -123,7 +123,34 @@ describe("Python generator", () => { ); const media = createWidget && requestMedia(createWidget); if (media) { - media[1].example = { description: null, name: "Widget", provider: "cloud", region: "us", settings: {} }; + media[1].schema = { + anyOf: [ + { + additionalProperties: false, + example: { labels: [{ bbox: [0.1, 0.2, 0.3, 0.4], classId: 0 }] }, + properties: { + labels: { + items: { + properties: { + bbox: { + items: { not: {} }, + prefixItems: Array.from({ length: 4 }, () => ({ type: "number" })), + type: "array", + }, + classId: { minimum: 0, type: "integer" }, + }, + required: ["classId"], + type: "object", + }, + type: "array", + }, + }, + required: ["labels"], + type: "object", + }, + { properties: { metadata: { type: "object" } }, required: ["metadata"], type: "object" }, + ], + }; } const echo = getOperations(source).find((operation) => operation.path === "/echo"); const echoMedia = echo && requestMedia(echo); @@ -144,7 +171,7 @@ describe("Python generator", () => { expect(sample?.source).toContain("response = client.widgets.retrieve("); expect(sample?.source).toContain('widget_id="widget_123"'); const createSample = decorated.paths["/widgets"]?.post?.["x-codeSamples"]?.[0]; - expect(createSample?.source).toContain('body={"description": None'); + expect(createSample?.source).toContain('body={"labels": [{"bbox": [0.1, 0.2, 0.3, 0.4], "classId": 0}]}'); const echoSample = decorated.paths["/echo"]?.post?.["x-codeSamples"]?.[0]; expect(echoSample?.source).toContain("body=None"); @@ -614,6 +641,13 @@ describe("Python generator", () => { ).toBe("examplexxx"); expect(schemaExample(document, { type: "null" })).toBeNull(); expect(schemaExample(document, { type: ["null"] })).toBeNull(); + expect( + schemaExample(document, { + items: { not: {} }, + prefixItems: Array.from({ length: 4 }, () => ({ type: "number" })), + type: "array", + }), + ).toEqual([1, 1, 1, 1]); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index dc32a2e..1b2e39b 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -26,7 +26,9 @@ export interface JsonSchema { minimum?: number; multipleOf?: number; nullable?: boolean; + not?: JsonSchema; oneOf?: JsonSchema[]; + prefixItems?: JsonSchema[]; properties?: Record; propertyNames?: JsonSchema; pattern?: string; @@ -536,12 +538,15 @@ export function pythonCodeSample( bodyValue?: unknown, ): string { const request = requestMedia(operation); + const generatedBody = request ? requestBodyExample(document, operation) : ""; const exampleBody = bodyValue !== undefined ? bodyValue : request?.[1].example !== undefined ? request[1].example - : schemaExample(document, request?.[1].schema); + : request?.[0].startsWith("text/") + ? generatedBody + : JSON.parse(generatedBody || "null"); const bodyValues = exampleBody && typeof exampleBody === "object" && !Array.isArray(exampleBody) ? (exampleBody as Record) @@ -1261,6 +1266,7 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch const schema = resolveSchema(document, input) ?? input; if (schema.const !== undefined && value !== schema.const) return false; if (schema.enum?.length && !schema.enum.includes(value)) return false; + if (schema.not && schemaMatches(document, value, schema.not, depth + 1)) return false; if (schema.allOf?.some((item) => !schemaMatches(document, value, item, depth + 1))) return false; if ( schema.oneOf?.length && @@ -1286,7 +1292,18 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch if (Array.isArray(value)) { if (schema.minItems !== undefined && value.length < schema.minItems) return false; if (schema.maxItems !== undefined && value.length > schema.maxItems) return false; - if (schema.items && value.some((item) => !schemaMatches(document, item, schema.items as JsonSchema, depth + 1))) + if ( + schema.prefixItems?.some( + (item, index) => index < value.length && !schemaMatches(document, value[index], item, depth + 1), + ) + ) + return false; + if ( + schema.items && + value + .slice(schema.prefixItems?.length ?? 0) + .some((item) => !schemaMatches(document, item, schema.items as JsonSchema, depth + 1)) + ) return false; } if (value && typeof value === "object" && !Array.isArray(value)) { @@ -1495,8 +1512,17 @@ export function schemaExample( if (schema.type === "null" || (Array.isArray(schema.type) && schema.type.every((value) => value === "null"))) return null; if (type === "array") { - const length = schema.maxItems === 0 ? 0 : Math.max(1, schema.minItems ?? 0); - return Array.from({ length }, () => schemaExample(document, schema.items, depth + 1, name)); + const prefix = (schema.prefixItems ?? []).map((item) => schemaExample(document, item, depth + 1, name)); + const length = Math.min( + schema.maxItems ?? Number.POSITIVE_INFINITY, + Math.max(prefix.length, 1, schema.minItems ?? 0), + ); + return [ + ...prefix.slice(0, length), + ...Array.from({ length: Math.max(0, length - prefix.length) }, () => + schemaExample(document, schema.items, depth + 1, name), + ), + ]; } if (type === "boolean") return false; if (type === "integer" || type === "number") { From a92b5275a850e9ecbcdae6c633a0d4d3e8f8dc20 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 09:06:58 +0200 Subject: [PATCH 2/2] Limit tuple generation to prefix items --- lib/generators/python.test.ts | 2 -- lib/openapi.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index a0a65ce..e06fcab 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -133,7 +133,6 @@ describe("Python generator", () => { items: { properties: { bbox: { - items: { not: {} }, prefixItems: Array.from({ length: 4 }, () => ({ type: "number" })), type: "array", }, @@ -643,7 +642,6 @@ describe("Python generator", () => { expect(schemaExample(document, { type: ["null"] })).toBeNull(); expect( schemaExample(document, { - items: { not: {} }, prefixItems: Array.from({ length: 4 }, () => ({ type: "number" })), type: "array", }), diff --git a/lib/openapi.ts b/lib/openapi.ts index 1b2e39b..553f7c7 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -26,7 +26,6 @@ export interface JsonSchema { minimum?: number; multipleOf?: number; nullable?: boolean; - not?: JsonSchema; oneOf?: JsonSchema[]; prefixItems?: JsonSchema[]; properties?: Record; @@ -1266,7 +1265,6 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch const schema = resolveSchema(document, input) ?? input; if (schema.const !== undefined && value !== schema.const) return false; if (schema.enum?.length && !schema.enum.includes(value)) return false; - if (schema.not && schemaMatches(document, value, schema.not, depth + 1)) return false; if (schema.allOf?.some((item) => !schemaMatches(document, value, item, depth + 1))) return false; if ( schema.oneOf?.length &&