66# ///
77from __future__ import annotations
88
9- from copy import deepcopy
109import pathlib
1110import os
1211import json
13- from typing_extensions import Any , NotRequired , TypedDict , TypeVar
12+ from typing_extensions import TypedDict , TypeVar
1413
1514here = pathlib .Path (__file__ ).parent
1615os .chdir (here )
1716
1817T = TypeVar ("T" )
1918
2019
21- def find_value (root , path ):
22- """
23- Find a value in an object graph.
24-
25- This function is used to follow the specified path through the object graph at root
26- and return the item in the graph, if any, that the path refers to.
27-
28- :param root: the root of the object graph to traverse.
29- :param path: the path through the graph to take.
30- :return: the resulting value or None.
31- """
32- if isinstance (path , str ):
33- path = path .split ("/" )
34- parent = root
35- for part in path :
36- if part in parent :
37- parent = parent [part ]
38- else :
39- return None
40- return parent
41-
42-
43- def expand_refs (
44- document , obj
45- ) -> Any : # Use `Any` for return type to hack around typing requirement
46- """
47- Expands `ref`s in the given object.
48-
49- Returns an object semantically equivalent to the original but with references expanded.
50-
51- Parameters
52- ----------
53- document
54- the master swagger document containing the responses and definitions.
55- obj
56- is either a normal swagger object, a ref object, or a swagger object with a schema.
57- """
58- if isinstance (obj , list ):
59- return [expand_refs (document , item ) for item in obj ]
60- elif isinstance (obj , dict ):
61- if "$ref" in obj :
62- ref_path = obj ["$ref" ].strip ("#/" ).split ("/" )
63- ref_value = find_value (document , ref_path )
64- if ref_value is None :
65- raise RuntimeError (
66- f"Reference { obj ['$ref' ]} not found in the document."
67- )
68- return expand_refs (document , ref_value )
69- else :
70- return {key : expand_refs (document , value ) for key , value in obj .items ()}
71- else :
72- return obj
73-
74-
7520class SwaggerOperation (TypedDict , total = False ):
76- operationId : str
77- tags : list [str ]
7821 summary : str
79- description : str
80- parameters : list [dict [str , Any ]]
81- responses : dict [str , Any ]
8222
8323
8424class SwaggerDocument (TypedDict ):
85- paths : NotRequired [dict [str , SwaggerOperation ]]
86- parameters : NotRequired [dict [str , Any ]]
87- responses : NotRequired [dict [str , Any ]]
88- definitions : NotRequired [dict [str , Any ]]
25+ paths : dict [str , SwaggerOperation ]
8926
9027
91- def expand_all_references (document : SwaggerDocument ) -> SwaggerDocument :
92- """
93- Expands all JSON references.
28+ class Route (TypedDict ):
29+ method : str
30+ path : str
31+ summary : str
9432
95- Expands all references ($ref) in the merged swagger document by replacing them with
96- their full definitions.
9733
98- This returns a new document with all references expanded.
34+ def transform_swagger_to_routes (
35+ swagger_dict : SwaggerDocument ,
36+ ) -> list [Route ]:
37+ """
38+ Swagger to routes.
39+
40+ Transforms the structure of a Swagger object to create a list where each object includes the route method, route path, and route summary.
9941
10042 Arguments
10143 ---------
102- document
103- The dictionary representing the Swagger document to process
44+ swagger_dict
45+ The dictionary representing the Swagger document.
10446
10547 Returns
10648 -------
10749 :
108- The processed Swagger document with all references expanded .
50+ A list of dictionaries where each dictionary includes the method, path, and summary of an API route .
10951 """
110- ret_document = deepcopy (document )
111- # List of error response keys to ignore
112- error_responses = [
113- "BadRequest" ,
114- "Unauthorized" ,
115- "PaymentRequired" ,
116- "Forbidden" ,
117- "NotFound" ,
118- "Conflict" ,
119- "APIError" ,
120- "InternalServerError" ,
121- ]
122-
123- # We need to expand refs in paths
124- if "paths" in ret_document :
125- for _path , operations in ret_document ["paths" ].items ():
126- for _method , operation in operations .items ():
127- if not isinstance (operation , dict ):
128- continue
129- # Expand refs in parameters
130- if "parameters" in operation :
131- operation ["parameters" ] = expand_refs (
132- ret_document , operation ["parameters" ]
133- )
52+ routes : list [Route ] = []
13453
135- # Expand refs in responses
136- if "responses" in operation :
137- for code , response in operation ["responses" ].items ():
138- if "schema" in response and code not in error_responses :
139- response ["schema" ] = expand_refs (
140- ret_document , response ["schema" ]
141- )
142-
143- # Expand refs in top-level parameters
144- if "parameters" in ret_document :
145- ret_document ["parameters" ] = expand_refs (
146- ret_document , ret_document ["parameters" ]
54+ if "paths" not in swagger_dict :
55+ raise ValueError (
56+ "The Swagger document `swagger_dict=` does not contain a 'paths' key."
14757 )
14858
149- # Expand refs in top-level responses, ignoring error responses
150- if "responses" in ret_document :
151- for response_key , response_value in ret_document ["responses" ].items ():
152- if response_key not in error_responses :
153- ret_document ["responses" ][response_key ] = expand_refs (
154- ret_document , response_value
59+ if "paths" in swagger_dict :
60+ for path , operations in swagger_dict ["paths" ].items ():
61+ if not isinstance (path , str ):
62+ raise ValueError (
63+ f"Expected route to be a string, but got { type (path )} ."
15564 )
65+ for method , operation in operations .items ():
66+ if not isinstance (method , str ):
67+ raise ValueError (
68+ f"Expected method to be a string, but got { type (method )} ."
69+ )
70+ if not isinstance (operation , dict ):
71+ raise ValueError (
72+ f"Expected operation to be a dictionary, but got { type (operation )} ."
73+ )
74+ if "summary" not in operation :
75+ raise ValueError (
76+ f"Expected operation to have a 'summary' key, but got { operation } ."
77+ )
15678
157- # Expand refs in definitions
158- if "definitions" in ret_document :
159- ret_document [ "definitions" ] = expand_refs (
160- ret_document , ret_document [ "definitions" ]
161- )
79+ summary = operation [ "summary" ]
80+ if not isinstance ( summary , str ) :
81+ raise ValueError (
82+ f"Expected summary to be a string, but got { type ( summary ) } ."
83+ )
16284
163- return ret_document
85+ routes .append (
86+ {
87+ "method" : method ,
88+ "path" : path ,
89+ "summary" : summary ,
90+ }
91+ )
92+
93+ return routes
16494
16595
166- def require_swagger ():
96+ def main ():
16797 if not (here / "_swagger.json" ).exists ():
16898 import urllib .request
16999
@@ -172,81 +102,27 @@ def require_swagger():
172102 here / "_swagger.json" ,
173103 )
174104
175- doc = json .load (here / "_swagger.json" )
176-
177- swagger = expand_all_references (doc )
178- return swagger
179-
180-
181- class OperationDef (TypedDict ):
182- name : str
183- tags : list [str ]
184- method : str
185- route : str
186- definition : dict [str , Any ]
187-
188-
189- def transform_swagger_to_operation_dict (
190- swagger_dict : SwaggerDocument ,
191- ) -> dict [str , OperationDef ]:
192- """
193- Swagger to operation dictionary transformation.
194-
195- Transforms the structure of a Swagger dictionary to create a dictionary where each entry key is
196- the operation ID and the value is the definition for that operation, including the HTTP verb
197- and the route.
198-
199- Args:
200- swagger_dict: The dictionary representing the Swagger document.
201-
202- Returns
203- -------
204- :
205- A dictionary where each key is an operation ID and the value is the operation definition.
206- """
207- operation_dict = {}
208-
209- if "paths" in swagger_dict :
210- for route , operations in swagger_dict ["paths" ].items ():
211- for method , operation in operations .items ():
212- if not isinstance (operation , dict ):
213- continue
214- if "operationId" in operation :
215- operation_id = operation ["operationId" ]
216- tags = operation ["tags" ] if "tags" in operation else []
217- # response_parent = operation["responses"]["200"] or operation["responses"]["204"]
218- # if response_parent and "response"
219- operation_dict [operation_id ] = {
220- "name" : operation_id ,
221- "tags" : tags ,
222- "method" : method ,
223- "route" : route ,
224- "definition" : operation ,
225- }
226-
227- return operation_dict
228-
229-
230- def main ():
231- swagger = require_swagger ()
105+ swagger = json .loads ((here / "_swagger.json" ).read_text ())
232106
233- operations = transform_swagger_to_operation_dict (swagger )
107+ routes = transform_swagger_to_routes (swagger )
234108
109+ # Write out the swagger portion of the instructions with a preamble and a
110+ # list of all the API routes and their short summaries.
235111 with open (here / "_swagger_prompt.md" , "w" ) as f :
236112 f .write (
237113 "If an answer can not be resolved, suggest to the user that they can explore calling these API routes themselves. Never produce code that calls these routes as we do not know the return type or successful status codes.\n \n API Routes:\n "
238114 ""
239115 )
240116
241- for operation in operations . values () :
242- # `"GET /v1/tasks/{id} Get task details"`
117+ for route in routes :
118+ # `"* GET /v1/tasks/{id} Get task details"`
243119 f .write (
244120 "* "
245- + operation ["method" ].upper ()
121+ + route ["method" ].upper ()
246122 + " "
247- + operation [ "route " ]
123+ + route [ "path " ]
248124 + " "
249- + operation [ "definition" ] ["summary" ].replace ("\n " , " " ).strip ()
125+ + route ["summary" ].replace ("\n " , " " ).strip ()
250126 + "\n " ,
251127 )
252128
0 commit comments