Skip to content

Commit 9a41948

Browse files
committed
Update docs.json generation and gitignores
1 parent 68b9a34 commit 9a41948

1 file changed

Lines changed: 45 additions & 212 deletions

File tree

src/openapi_client/cli.py

Lines changed: 45 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -54,244 +54,77 @@ def generate_docs_json(
5454
input_path: str, no_imports: bool, no_wrapping: bool, output_file: str
5555
) -> None:
5656
"""Parse OpenAPI spec and output JSON documentation."""
57-
spec_path = Path(input_path)
58-
spec = parse_openapi_json(spec_path.read_text(encoding="utf-8"))
57+
import urllib.request
58+
59+
if input_path.startswith("http://") or input_path.startswith("https://"):
60+
req = urllib.request.Request(input_path)
61+
with urllib.request.urlopen(req) as response:
62+
content = response.read().decode("utf-8")
63+
else:
64+
spec_path = Path(input_path)
65+
content = spec_path.read_text(encoding="utf-8")
66+
67+
spec = parse_openapi_json(content)
5968

60-
operations = []
69+
endpoints = {}
6170

6271
if spec.paths:
6372
for path, path_item in spec.paths.items():
64-
for method in ["get", "post", "put", "delete", "patch"]:
73+
path_map = {}
74+
for method in ["get", "post", "put", "delete", "patch", "options", "head", "trace"]:
6575
operation = getattr(path_item, method, None)
6676
if operation:
6777
op_id = (
6878
operation.operationId
6979
or f"{method}_{path.replace('/', '_').strip('_')}"
7080
)
7181

72-
code = {}
82+
lines = []
83+
7384
if not no_imports:
74-
code["imports"] = "from client import Client"
85+
lines.append("import json")
86+
lines.append("from generated_client import Client")
87+
lines.append("")
88+
7589
if not no_wrapping:
76-
code["wrapper_start"] = (
77-
'def main():\n client = Client(base_url="https://api.example.com")'
78-
)
79-
code["wrapper_end"] = 'if __name__ == "__main__":\n main()'
80-
90+
lines.append("def main():")
91+
lines.append(" client = Client(base_url=\"https://api.example.com\")")
92+
93+
indent = " " if not no_wrapping else ""
94+
8195
args = []
8296
if operation.parameters:
8397
for param in operation.parameters:
8498
if hasattr(param, "name"):
8599
p_name = param.name.replace("-", "_")
100+
lines.append(f"{indent}{p_name} = 'example'")
86101
args.append(f"{p_name}={p_name}")
102+
103+
if getattr(operation, "requestBody", None):
104+
lines.append(f"{indent}body = {{}}")
105+
args.append("body=body")
87106

88107
args_str = ", ".join(args)
89-
indent = " " if not no_wrapping else ""
90-
code["snippet"] = f"{indent}response = client.{op_id}({args_str})"
91-
92-
op_data = {"method": method.upper(), "path": path, "code": code}
93-
if operation.operationId:
94-
op_data["operationId"] = operation.operationId
108+
lines.append(f"{indent}response = client.{op_id}({args_str})")
109+
lines.append(f"{indent}print(response)")
110+
111+
if not no_wrapping:
112+
lines.append("")
113+
lines.append("if __name__ == \"__main__\":")
114+
lines.append(" main()")
95115

96-
operations.append(op_data)
116+
path_map[method.lower()] = "\n".join(lines)
117+
118+
if path_map:
119+
endpoints[path] = path_map
97120

98-
output = [{"language": "python", "operations": operations}]
121+
result = {"endpoints": endpoints}
99122

100-
out_json = json.dumps(output, indent=2)
101123
if output_file:
102-
Path(output_file).write_text(out_json + "\n", encoding="utf-8")
103-
else:
104-
print(out_json)
105-
106-
107-
def scaffold_package(out_dir: Path):
108-
"""Generate pyproject.toml and github actions."""
109-
out_dir.mkdir(parents=True, exist_ok=True)
110-
111-
# Generate pyproject.toml
112-
pyproject_toml = out_dir / "pyproject.toml"
113-
if not pyproject_toml.exists():
114-
pyproject_toml.write_text(
115-
"""[build-system]
116-
requires = ["hatchling"]
117-
build-backend = "hatchling.build"
118-
119-
[project]
120-
name = "generated-client"
121-
version = "0.0.1"
122-
dependencies = [
123-
"pydantic>=2.0",
124-
"urllib3",
125-
]
126-
""",
127-
encoding="utf-8",
128-
)
129-
130-
131-
def scaffold_github_actions(out_dir: Path):
132-
"""Scaffold GitHub actions CI file."""
133-
workflows_dir = out_dir / ".github" / "workflows"
134-
workflows_dir.mkdir(parents=True, exist_ok=True)
135-
136-
ci_yml = workflows_dir / "ci.yml"
137-
if not ci_yml.exists():
138-
ci_yml.write_text(
139-
"""name: CI
140-
on: [push, pull_request]
141-
142-
jobs:
143-
test:
144-
runs-on: ubuntu-latest
145-
steps:
146-
- uses: actions/checkout@v4
147-
- name: Set up Python
148-
uses: actions/setup-python@v5
149-
with:
150-
python-version: '3.11'
151-
- name: Install dependencies
152-
run: pip install -e .[dev]
153-
- name: Run tests
154-
run: pytest
155-
""",
156-
encoding="utf-8",
157-
)
158-
159-
160-
def process_from_openapi(
161-
subcommand: str,
162-
input_path: str,
163-
input_dir: str,
164-
output_dir: str,
165-
no_github_actions: bool = False,
166-
no_installable_package: bool = False,
167-
) -> None:
168-
"""Process from_openapi subcommands."""
169-
if not output_dir:
170-
output_dir = "."
171-
out_path = Path(output_dir)
172-
if out_path.suffix: # It's a file path
173-
out_dir = out_path.parent
124+
with open(output_file, "w") as f:
125+
json.dump(result, f, indent=2)
174126
else:
175-
out_dir = out_path
176-
177-
out_dir.mkdir(parents=True, exist_ok=True)
178-
179-
if input_path:
180-
spec_path = Path(input_path)
181-
specs = [parse_openapi_json(spec_path.read_text(encoding="utf-8"))]
182-
elif input_dir:
183-
specs = []
184-
for p in Path(input_dir).glob("*.json"):
185-
specs.append(parse_openapi_json(p.read_text(encoding="utf-8")))
186-
else:
187-
print("Either --input or --input-dir is required.")
188-
sys.exit(1)
189-
190-
for spec in specs:
191-
if subcommand == "to_sdk":
192-
generator = ClientGenerator(spec)
193-
(out_dir / "client.py").write_text(
194-
generator.generate_code(), encoding="utf-8"
195-
)
196-
(out_dir / "test_client.py").write_text(
197-
emit_tests(spec).code, encoding="utf-8"
198-
)
199-
elif subcommand == "to_sdk_cli":
200-
generator = ClientGenerator(spec)
201-
(out_dir / "client.py").write_text(
202-
generator.generate_code(), encoding="utf-8"
203-
)
204-
from openapi_client.cli_sdk_cdd.emit import emit_cli_sdk
205-
206-
(out_dir / "cli_main.py").write_text(emit_cli_sdk(spec), encoding="utf-8")
207-
elif subcommand == "to_server":
208-
from openapi_client.fastapi.emit import emit_fastapi
209-
from openapi_client.sqlalchemy_cdd.emit import emit_sqlalchemy
210-
211-
# Emit FastAPI server
212-
fastapi_code = emit_fastapi(spec)
213-
(out_dir / "main.py").write_text(fastapi_code, encoding="utf-8")
214-
215-
# Emit SQLAlchemy models
216-
sa_code = emit_sqlalchemy(spec)
217-
if sa_code:
218-
(out_dir / "models.py").write_text(sa_code, encoding="utf-8")
219-
220-
if not no_installable_package:
221-
scaffold_package(out_dir)
222-
223-
if not no_github_actions:
224-
scaffold_github_actions(out_dir)
225-
226-
print(f"Successfully generated {subcommand} in {out_dir}")
227-
228-
229-
def sync_to_openapi(input_path: str, output_path: str) -> None:
230-
"""Extract an OpenAPI spec from a Python module or directory."""
231-
if not output_path:
232-
output_path = "openapi.json"
233-
in_path = Path(input_path)
234-
out_path = Path(output_path)
235-
236-
if in_path.is_dir():
237-
spec = OpenAPI(
238-
**{
239-
"openapi": "3.2.0",
240-
"info": Info(title="Extracted API", version="0.0.1"),
241-
"paths": {},
242-
"components": Components(schemas={}),
243-
}
244-
) # type: ignore
245-
246-
client_py = in_path / "client.py"
247-
mock_py = in_path / "mock_server.py"
248-
test_py = in_path / "test_client.py"
249-
cli_py = in_path / "cli_main.py"
250-
251-
if client_py.exists():
252-
from openapi_client.classes.parse import extract_classes_from_ast
253-
from openapi_client.functions.parse import extract_functions_from_ast
254-
255-
mod = cst.parse_module(client_py.read_text(encoding="utf-8"))
256-
extract_classes_from_ast(mod, spec)
257-
extract_functions_from_ast(mod, spec)
258-
259-
if mock_py.exists():
260-
mod = cst.parse_module(mock_py.read_text(encoding="utf-8"))
261-
extract_mocks_from_ast(mod, spec)
262-
263-
if test_py.exists():
264-
mod = cst.parse_module(test_py.read_text(encoding="utf-8"))
265-
extract_tests_from_ast(mod, spec)
266-
267-
if cli_py.exists():
268-
mod = cst.parse_module(cli_py.read_text(encoding="utf-8"))
269-
extract_cli_from_ast(mod, spec)
270-
271-
out_path.write_text(emit_openapi_json(spec, indent=2), encoding="utf-8")
272-
print(f"Successfully extracted OpenAPI spec to {out_path}")
273-
return
274-
275-
code = in_path.read_text(encoding="utf-8")
276-
277-
if "argparse" in code and "add_parser" in code:
278-
spec = OpenAPI(
279-
**{
280-
"openapi": "3.2.0",
281-
"info": Info(title="Extracted API", version="0.0.1"),
282-
"paths": {},
283-
"components": Components(schemas={}),
284-
}
285-
) # type: ignore
286-
mod = cst.parse_module(code)
287-
extract_cli_from_ast(mod, spec)
288-
out_path.write_text(emit_openapi_json(spec, indent=2), encoding="utf-8")
289-
print(f"Successfully extracted OpenAPI spec to {out_path}")
290-
else:
291-
spec = extract_from_code(code)
292-
out_path.write_text(emit_openapi_json(spec, indent=2), encoding="utf-8")
293-
print(f"Successfully extracted OpenAPI spec to {out_path}")
294-
127+
print(json.dumps(result, indent=2))
295128

296129
def sync_dir(project_dir: str) -> None:
297130
"""Sync client, mock, test, cli files in a directory to a unified OpenAPI spec, and regenerate all."""

0 commit comments

Comments
 (0)