mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
OpenAPI: Support Python code sample
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': minor
|
||||
---
|
||||
|
||||
Support Python code sample
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type HTMLAttributes, useMemo } from 'react';
|
||||
import * as Base from 'fumadocs-ui/components/codeblock';
|
||||
import { createHighlighter, bundledLanguages, bundledThemes } from 'shiki';
|
||||
import { sharedTransformers } from '@/utils/shiki';
|
||||
|
||||
export type CodeBlockProps = HTMLAttributes<HTMLPreElement> & {
|
||||
code: string;
|
||||
@@ -22,6 +23,7 @@ export function CodeBlock({
|
||||
lang,
|
||||
defaultColor: false,
|
||||
themes: { light: 'github-light', dark: 'github-dark' },
|
||||
transformers: sharedTransformers,
|
||||
});
|
||||
}, [code, lang]);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { generateSample, type EndpointSample } from '@/schema/sample';
|
||||
import * as CURL from '@/requests/curl';
|
||||
import * as JS from '@/requests/javascript';
|
||||
import * as Go from '@/requests/go';
|
||||
import * as Python from '@/requests/python';
|
||||
import { type MethodInformation, type RenderContext } from '@/types';
|
||||
import { noRef, getPreferredType } from '@/utils/schema';
|
||||
import { getTypescriptSchema } from '@/utils/get-typescript-schema';
|
||||
@@ -176,6 +177,11 @@ async function APIExample({
|
||||
source: Go.getSampleRequest(endpoint),
|
||||
lang: 'go',
|
||||
},
|
||||
{
|
||||
label: 'Python',
|
||||
source: Python.getSampleRequest(endpoint),
|
||||
lang: 'python',
|
||||
},
|
||||
...(ctx.generateCodeSamples ? await ctx.generateCodeSamples(endpoint) : []),
|
||||
...((method as CustomProperty)['x-codeSamples'] ?? []),
|
||||
]).filter((item) => item.source !== undefined) as Required<CodeSample>[];
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { type EndpointSample } from '@/schema/sample';
|
||||
|
||||
export function getSampleRequest(endpoint: EndpointSample): string {
|
||||
const headers = new Map<string, unknown>();
|
||||
const cookies = new Map<string, unknown>();
|
||||
const variables = new Map<string, string>();
|
||||
|
||||
for (const param of endpoint.parameters) {
|
||||
if (param.in === 'header') headers.set(param.name, param.sample);
|
||||
if (param.in === 'cookie') cookies.set(param.name, param.sample);
|
||||
}
|
||||
|
||||
if (headers.size > 0) {
|
||||
variables.set(
|
||||
'headers',
|
||||
JSON.stringify(Object.fromEntries(headers.entries()), null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
if (cookies.size > 0) {
|
||||
variables.set(
|
||||
'cookies',
|
||||
JSON.stringify(Object.fromEntries(cookies.entries()), null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
if (endpoint.body) {
|
||||
variables.set(
|
||||
endpoint.body.mediaType === 'multipart/form-data' ? 'data' : 'json',
|
||||
JSON.stringify(endpoint.body.sample, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
return `import requests
|
||||
|
||||
url = ${JSON.stringify(endpoint.url)}
|
||||
${Array.from(variables.entries())
|
||||
.map(([k, v]) => `${k} = ${v}`)
|
||||
.join('\n')}
|
||||
response = requests.request("${endpoint.method}", url${
|
||||
variables.size > 0
|
||||
? `, ${Array.from(variables.keys())
|
||||
.map((k) => `${k}=${k}`)
|
||||
.join(', ')}`
|
||||
: ''
|
||||
})
|
||||
|
||||
print(response.text)`;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type HTMLAttributes, useLayoutEffect, useState } from 'react';
|
||||
import * as Base from 'fumadocs-ui/components/codeblock';
|
||||
import { useApiContext } from '@/ui/contexts/api';
|
||||
import { sharedTransformers } from '@/utils/shiki';
|
||||
|
||||
export type CodeBlockProps = HTMLAttributes<HTMLPreElement> & {
|
||||
code: string;
|
||||
@@ -22,6 +23,7 @@ export function CodeBlock({
|
||||
lang,
|
||||
defaultColor: false,
|
||||
themes: { light: 'github-light', dark: 'github-dark' },
|
||||
transformers: sharedTransformers,
|
||||
});
|
||||
|
||||
setHtml(themedHtml);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type ShikiTransformer } from 'shiki';
|
||||
|
||||
export const sharedTransformers: ShikiTransformer[] = [
|
||||
{
|
||||
name: 'fumadocs:pre-process',
|
||||
line(hast) {
|
||||
if (hast.children.length === 0) {
|
||||
// Keep the empty lines when using grid layout
|
||||
hast.children.push({
|
||||
type: 'text',
|
||||
value: ' ',
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,64 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import Parser from '@apidevtools/json-schema-ref-parser';
|
||||
import { type OpenAPIV3 as OpenAPI } from 'openapi-types';
|
||||
import Slugger from 'github-slugger';
|
||||
import * as Python from '@/requests/python';
|
||||
import * as Go from '@/requests/go';
|
||||
import * as Curl from '@/requests/curl';
|
||||
import * as JS from '@/requests/javascript';
|
||||
import { generateSample } from '@/schema/sample';
|
||||
import { createMethod } from '@/schema/method';
|
||||
import { methodKeys } from '@/build-routes';
|
||||
import { defaultRenderer } from '@/render/renderer';
|
||||
import { type RenderContext } from '@/types';
|
||||
|
||||
const example = fileURLToPath(
|
||||
new URL('./fixtures/petstore.yaml', import.meta.url),
|
||||
);
|
||||
const document = await Parser.dereference<OpenAPI.Document>(example);
|
||||
|
||||
const ctx: RenderContext = {
|
||||
baseUrl: 'http://localhost:8080',
|
||||
document,
|
||||
renderer: defaultRenderer,
|
||||
slugger: new Slugger(),
|
||||
};
|
||||
|
||||
describe('Code Sample Generators', () => {
|
||||
for (const [path, item] of Object.entries(document.paths)) {
|
||||
if (!item) continue;
|
||||
|
||||
for (const method of methodKeys) {
|
||||
const operation = item[method];
|
||||
if (!operation) continue;
|
||||
|
||||
const info = createMethod(method, item, operation);
|
||||
const endpoint = generateSample(path, info, ctx);
|
||||
|
||||
test(`Go: ${method} ${path}`, async () => {
|
||||
await expect(Go.getSampleRequest(endpoint)).toMatchFileSnapshot(
|
||||
`./out/samples/${path}/${method}.go`,
|
||||
);
|
||||
});
|
||||
|
||||
test(`Curl: ${method} ${path}`, async () => {
|
||||
await expect(Curl.getSampleRequest(endpoint)).toMatchFileSnapshot(
|
||||
`./out/samples/${path}/${method}.bash`,
|
||||
);
|
||||
});
|
||||
|
||||
test(`Python: ${method} ${path}`, async () => {
|
||||
await expect(Python.getSampleRequest(endpoint)).toMatchFileSnapshot(
|
||||
`./out/samples/${path}/${method}.py`,
|
||||
);
|
||||
});
|
||||
|
||||
test(`JavaScript: ${method} ${path}`, async () => {
|
||||
await expect(JS.getSampleRequest(endpoint)).toMatchFileSnapshot(
|
||||
`./out/samples/${path}/${method}.js`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
curl -X GET "http://localhost:8080/pets?limit=100"
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
url := "http://localhost:8080/pets?limit=100"
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
defer res.Body.Close()
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
|
||||
fmt.Println(res)
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fetch("http://localhost:8080/pets?limit=100", {
|
||||
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import requests
|
||||
|
||||
url = "http://localhost:8080/pets?limit=100"
|
||||
|
||||
response = requests.request("GET", url)
|
||||
|
||||
print(response.text)
|
||||
@@ -0,0 +1,7 @@
|
||||
curl -X POST "http://localhost:8080/pets" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"id": 0,
|
||||
"name": "string",
|
||||
"tag": "string"
|
||||
}'
|
||||
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
url := "http://localhost:8080/pets"
|
||||
payload := strings.NewReader(`{
|
||||
"id": 0,
|
||||
"name": "string",
|
||||
"tag": "string"
|
||||
}`)
|
||||
|
||||
req, _ := http.NewRequest("POST", url, payload)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
defer res.Body.Close()
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
|
||||
fmt.Println(res)
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fetch("http://localhost:8080/pets", {
|
||||
body: JSON.stringify({
|
||||
"id": 0,
|
||||
"name": "string",
|
||||
"tag": "string"
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import requests
|
||||
|
||||
url = "http://localhost:8080/pets"
|
||||
json = {
|
||||
"id": 0,
|
||||
"name": "string",
|
||||
"tag": "string"
|
||||
}
|
||||
response = requests.request("POST", url, json=json)
|
||||
|
||||
print(response.text)
|
||||
@@ -0,0 +1 @@
|
||||
curl -X GET "http://localhost:8080/pets/string"
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
url := "http://localhost:8080/pets/string"
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
defer res.Body.Close()
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
|
||||
fmt.Println(res)
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fetch("http://localhost:8080/pets/string", {
|
||||
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import requests
|
||||
|
||||
url = "http://localhost:8080/pets/string"
|
||||
|
||||
response = requests.request("GET", url)
|
||||
|
||||
print(response.text)
|
||||
Reference in New Issue
Block a user