@@ -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
296129def 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