Skip to content

Commit 361ccc8

Browse files
feat(tools): add gen_openapi script to generate OpenAPI spec (JSON/YAML)
Usage: python -m benchmesh_service.tools.gen_openapi --help Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 89b3a67 commit 361ccc8

4 files changed

Lines changed: 122 additions & 49 deletions

File tree

benchmesh-serial-service/src/benchmesh_service/doc/openapi.json

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"content": {
1616
"application/json": {
1717
"schema": {
18-
"additionalProperties": true,
1918
"type": "object",
2019
"title": "Response Get Status Status Get"
2120
}
@@ -51,40 +50,40 @@
5150
"operationId": "call_driver_get_instruments__klass___device_id___channel___method__get",
5251
"parameters": [
5352
{
54-
"name": "klass",
55-
"in": "path",
5653
"required": true,
5754
"schema": {
5855
"type": "string",
5956
"title": "Klass"
60-
}
57+
},
58+
"name": "klass",
59+
"in": "path"
6160
},
6261
{
63-
"name": "device_id",
64-
"in": "path",
6562
"required": true,
6663
"schema": {
6764
"type": "string",
6865
"title": "Device Id"
69-
}
66+
},
67+
"name": "device_id",
68+
"in": "path"
7069
},
7170
{
72-
"name": "channel",
73-
"in": "path",
7471
"required": true,
7572
"schema": {
7673
"type": "string",
7774
"title": "Channel"
78-
}
75+
},
76+
"name": "channel",
77+
"in": "path"
7978
},
8079
{
81-
"name": "method",
82-
"in": "path",
8380
"required": true,
8481
"schema": {
8582
"type": "string",
8683
"title": "Method"
87-
}
84+
},
85+
"name": "method",
86+
"in": "path"
8887
}
8988
],
9089
"responses": {
@@ -115,49 +114,49 @@
115114
"operationId": "call_driver_post_instruments__klass___device_id___channel___method___param__post",
116115
"parameters": [
117116
{
118-
"name": "klass",
119-
"in": "path",
120117
"required": true,
121118
"schema": {
122119
"type": "string",
123120
"title": "Klass"
124-
}
121+
},
122+
"name": "klass",
123+
"in": "path"
125124
},
126125
{
127-
"name": "device_id",
128-
"in": "path",
129126
"required": true,
130127
"schema": {
131128
"type": "string",
132129
"title": "Device Id"
133-
}
130+
},
131+
"name": "device_id",
132+
"in": "path"
134133
},
135134
{
136-
"name": "channel",
137-
"in": "path",
138135
"required": true,
139136
"schema": {
140137
"type": "string",
141138
"title": "Channel"
142-
}
139+
},
140+
"name": "channel",
141+
"in": "path"
143142
},
144143
{
145-
"name": "method",
146-
"in": "path",
147144
"required": true,
148145
"schema": {
149146
"type": "string",
150147
"title": "Method"
151-
}
148+
},
149+
"name": "method",
150+
"in": "path"
152151
},
153152
{
154-
"name": "param",
155-
"in": "path",
156153
"required": true,
157154
"schema": {
158155
"type": "string",
159156
"title": "Param"
160-
}
157+
},
158+
"name": "param",
159+
"in": "path"
161160
}
162161
],
163162
"responses": {

benchmesh-serial-service/src/benchmesh_service/doc/openapi.yaml

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ paths:
1313
content:
1414
application/json:
1515
schema:
16-
additionalProperties: true
1716
type: object
1817
title: Response Get Status Status Get
1918
/instruments:
@@ -34,30 +33,30 @@ paths:
3433
summary: Call driver method (read-only)
3534
operationId: call_driver_get_instruments__klass___device_id___channel___method__get
3635
parameters:
37-
- name: klass
38-
in: path
39-
required: true
36+
- required: true
4037
schema:
4138
type: string
4239
title: Klass
43-
- name: device_id
40+
name: klass
4441
in: path
45-
required: true
42+
- required: true
4643
schema:
4744
type: string
4845
title: Device Id
49-
- name: channel
46+
name: device_id
5047
in: path
51-
required: true
48+
- required: true
5249
schema:
5350
type: string
5451
title: Channel
55-
- name: method
52+
name: channel
5653
in: path
57-
required: true
54+
- required: true
5855
schema:
5956
type: string
6057
title: Method
58+
name: method
59+
in: path
6160
responses:
6261
'200':
6362
description: Successful Response
@@ -75,36 +74,36 @@ paths:
7574
summary: Call driver method (write)
7675
operationId: call_driver_post_instruments__klass___device_id___channel___method___param__post
7776
parameters:
78-
- name: klass
79-
in: path
80-
required: true
77+
- required: true
8178
schema:
8279
type: string
8380
title: Klass
84-
- name: device_id
81+
name: klass
8582
in: path
86-
required: true
83+
- required: true
8784
schema:
8885
type: string
8986
title: Device Id
90-
- name: channel
87+
name: device_id
9188
in: path
92-
required: true
89+
- required: true
9390
schema:
9491
type: string
9592
title: Channel
96-
- name: method
93+
name: channel
9794
in: path
98-
required: true
95+
- required: true
9996
schema:
10097
type: string
10198
title: Method
102-
- name: param
99+
name: method
103100
in: path
104-
required: true
101+
- required: true
105102
schema:
106103
type: string
107104
title: Param
105+
name: param
106+
in: path
108107
responses:
109108
'200':
110109
description: Successful Response
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Tools module for benchmesh_service
2+
# Helper CLI scripts in this package:
3+
# - driver_cli: Manual driver testing utility
4+
# - gen_openapi: Generate OpenAPI spec from FastAPI app
5+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import argparse
2+
import json
3+
import os
4+
import sys
5+
from importlib import import_module
6+
7+
8+
def _default_out_dir() -> str:
9+
# tools/ -> package root = benchmesh_service
10+
pkg_root = os.path.dirname(os.path.dirname(__file__))
11+
return os.path.join(pkg_root, 'doc')
12+
13+
14+
def _load_app(app_path: str):
15+
# app_path format: module:var, e.g. benchmesh_service.api:app
16+
if ':' not in app_path:
17+
raise SystemExit("--app must be in the form 'module:var', e.g. benchmesh_service.api:app")
18+
mod_name, var_name = app_path.split(':', 1)
19+
mod = import_module(mod_name)
20+
app = getattr(mod, var_name, None)
21+
if app is None:
22+
raise SystemExit(f"Could not find attribute '{var_name}' in module '{mod_name}'")
23+
return app
24+
25+
26+
def build_parser() -> argparse.ArgumentParser:
27+
p = argparse.ArgumentParser(description="Generate OpenAPI spec from FastAPI app")
28+
p.add_argument('--app', default='benchmesh_service.api:app', help="App path in form module:var")
29+
p.add_argument('--out-dir', default=_default_out_dir(), help="Output directory for spec files")
30+
p.add_argument('--basename', default='openapi', help="Base filename without extension")
31+
p.add_argument('--formats', choices=['json','yaml','both'], default='both', help="Formats to write")
32+
return p
33+
34+
35+
def main(argv=None) -> int:
36+
args = build_parser().parse_args(argv)
37+
38+
# Load app lazily to avoid side effects on import
39+
app = _load_app(args.app)
40+
spec = app.openapi()
41+
42+
out_dir = os.path.abspath(args.out_dir)
43+
os.makedirs(out_dir, exist_ok=True)
44+
45+
wrote_any = False
46+
47+
if args.formats in ('json', 'both'):
48+
json_path = os.path.join(out_dir, f"{args.basename}.json")
49+
with open(json_path, 'w', encoding='utf-8') as f:
50+
json.dump(spec, f, ensure_ascii=False, indent=2)
51+
print(f"Wrote: {json_path}")
52+
wrote_any = True
53+
54+
if args.formats in ('yaml', 'both'):
55+
try:
56+
import yaml # type: ignore
57+
except Exception as e:
58+
print(f"Warning: PyYAML not available, skipping YAML output: {e}", file=sys.stderr)
59+
else:
60+
yaml_path = os.path.join(out_dir, f"{args.basename}.yaml")
61+
with open(yaml_path, 'w', encoding='utf-8') as f:
62+
yaml.safe_dump(spec, f, sort_keys=False, allow_unicode=True)
63+
print(f"Wrote: {yaml_path}")
64+
wrote_any = True
65+
66+
return 0 if wrote_any else 1
67+
68+
69+
if __name__ == '__main__':
70+
raise SystemExit(main())

0 commit comments

Comments
 (0)