Skip to content

Commit 2da0ccf

Browse files
committed
update code sample generator for python
1 parent 9e062b1 commit 2da0ccf

File tree

1 file changed

+144
-5
lines changed

1 file changed

+144
-5
lines changed

packages/react-openapi/src/code-samples.ts

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,33 @@ ${headerString}${bodyString}`;
156156
syntax: 'python',
157157
generate: ({ method, url: { origin, path }, headers, body }) => {
158158
const contentType = headers?.['Content-Type'];
159-
let code = `${isJSON(contentType) ? 'import json\n' : ''}import requests\n\n`;
159+
const needsJsonImport = body && isJSON(contentType) && typeof body === 'string';
160+
161+
let code = '';
162+
163+
// Import statements
164+
if (needsJsonImport) {
165+
code += 'import json\n';
166+
}
167+
code += 'import requests\n\n';
168+
169+
// Extract path parameters and create constants
170+
const { extractedParams, processedPath } = extractPathParameters(path);
171+
if (extractedParams.length > 0) {
172+
extractedParams.forEach(param => {
173+
code += `${param.constant} = "${param.placeholder}"\n`;
174+
});
175+
code += '\n';
176+
}
177+
178+
// Process headers to create better placeholders
179+
const processedHeaders = processPythonHeaders(headers);
180+
if (processedHeaders.constants.length > 0) {
181+
processedHeaders.constants.forEach(constant => {
182+
code += `${constant.name} = "${constant.placeholder}"\n`;
183+
});
184+
code += '\n';
185+
}
160186

161187
if (body) {
162188
const lines = BodyGenerators.getPythonBody(body, headers);
@@ -169,17 +195,31 @@ ${headerString}${bodyString}`;
169195
}
170196
}
171197

198+
// Build the request
199+
const urlStr = extractedParams.length > 0
200+
? `f"${origin}${processedPath}"`
201+
: `"${origin}${path}"`;
202+
172203
code += `response = requests.${method.toLowerCase()}(\n`;
173-
code += indent(`"${origin}${path}",\n`, 4);
204+
code += indent(`${urlStr},\n`, 4);
174205

175-
if (headers && Object.keys(headers).length > 0) {
176-
code += indent(`headers=${stringifyOpenAPI(headers)},\n`, 4);
206+
if (processedHeaders.headers && Object.keys(processedHeaders.headers).length > 0) {
207+
code += indent(`headers={\n`, 4);
208+
Object.entries(processedHeaders.headers).forEach(([key, value], index, array) => {
209+
const isLast = index === array.length - 1;
210+
code += indent(`"${key}": ${value}${isLast ? '' : ','}\n`, 8);
211+
});
212+
code += indent(`},\n`, 4);
177213
}
178214

179215
if (body) {
180216
if (body === 'files') {
181217
code += indent(`files=${body}\n`, 4);
182-
} else if (isJSON(contentType)) {
218+
} else if (isJSON(contentType) && isPlainObject(body)) {
219+
// Use json parameter for dict objects
220+
code += indent(`json=${body}\n`, 4);
221+
} else if (isJSON(contentType) && needsJsonImport) {
222+
// Use data=json.dumps() for JSON strings
183223
code += indent(`data=json.dumps(${body})\n`, 4);
184224
} else {
185225
code += indent(`data=${body}\n`, 4);
@@ -372,7 +412,29 @@ const BodyGenerators = {
372412
} else if (isYAML(contentType)) {
373413
code += `yamlBody = \"\"\"\n${indent(yaml.dump(body), 4)}\"\"\"\n\n`;
374414
body = 'yamlBody';
415+
} else if (isJSON(contentType) && isPlainObject(body)) {
416+
// For dict objects, return as-is to use with json= parameter
417+
body = stringifyOpenAPI(
418+
body,
419+
(_key, value) => {
420+
switch (value) {
421+
case true:
422+
return '$$__TRUE__$$';
423+
case false:
424+
return '$$__FALSE__$$';
425+
case null:
426+
return '$$__NULL__$$';
427+
default:
428+
return value;
429+
}
430+
},
431+
2
432+
)
433+
.replaceAll('"$$__TRUE__$$"', 'True')
434+
.replaceAll('"$$__FALSE__$$"', 'False')
435+
.replaceAll('"$$__NULL__$$"', 'None');
375436
} else {
437+
// For everything else (including JSON strings)
376438
body = stringifyOpenAPI(
377439
body,
378440
(_key, value) => {
@@ -487,3 +549,80 @@ function buildHeredoc(lines: string[]): string {
487549
}
488550
return result;
489551
}
552+
553+
/**
554+
* Extracts path parameters and converts them to Python constants
555+
*/
556+
function extractPathParameters(path: string): {
557+
extractedParams: Array<{ constant: string; placeholder: string; param: string }>;
558+
processedPath: string;
559+
} {
560+
const extractedParams: Array<{ constant: string; placeholder: string; param: string }> = [];
561+
let processedPath = path;
562+
563+
// Find all path parameters in the format {paramName}
564+
const paramMatches = path.match(/\{([^}]+)\}/g);
565+
566+
if (paramMatches) {
567+
paramMatches.forEach(match => {
568+
const paramName = match.slice(1, -1); // Remove { and }
569+
// Convert camelCase to SNAKE_CASE
570+
const constantName = paramName
571+
.replace(/([a-z])([A-Z])/g, '$1_$2')
572+
.toUpperCase();
573+
const placeholder = `<your ${paramName.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase()}>`;
574+
575+
extractedParams.push({
576+
constant: constantName,
577+
placeholder: placeholder,
578+
param: paramName
579+
});
580+
581+
// Replace {paramName} with {CONSTANT_NAME} for f-string
582+
processedPath = processedPath.replace(match, `{${constantName}}`);
583+
});
584+
}
585+
586+
return { extractedParams, processedPath };
587+
}
588+
589+
/**
590+
* Processes headers to create Python constants and clean formatting
591+
*/
592+
function processPythonHeaders(headers?: Record<string, string>): {
593+
constants: Array<{ name: string; placeholder: string }>;
594+
headers: Record<string, string>;
595+
} {
596+
if (!headers) {
597+
return { constants: [], headers: {} };
598+
}
599+
600+
const constants: Array<{ name: string; placeholder: string }> = [];
601+
const processedHeaders: Record<string, string> = {};
602+
603+
Object.entries(headers).forEach(([key, value]) => {
604+
if (key === 'Authorization' && value.includes('Bearer')) {
605+
// Extract token constants
606+
const constantName = 'API_TOKEN';
607+
const placeholder = '<your gitbook api token>';
608+
constants.push({ name: constantName, placeholder });
609+
processedHeaders[key] = `f"Bearer {${constantName}}"`;
610+
} else if (key === 'Authorization' && value.includes('Basic')) {
611+
const constantName = 'API_TOKEN';
612+
const placeholder = '<your basic auth token>';
613+
constants.push({ name: constantName, placeholder });
614+
processedHeaders[key] = `f"Basic {${constantName}}"`;
615+
} else if (value.includes('YOUR_') || value.includes('TOKEN')) {
616+
// Generic token handling
617+
const constantName = 'API_TOKEN';
618+
const placeholder = '<your api token>';
619+
constants.push({ name: constantName, placeholder });
620+
processedHeaders[key] = `f"Bearer {${constantName}}"`;
621+
} else {
622+
// Regular headers
623+
processedHeaders[key] = `"${value}"`;
624+
}
625+
});
626+
627+
return { constants, headers: processedHeaders };
628+
}

0 commit comments

Comments
 (0)