Skip to content

Commit ba6bb95

Browse files
committed
Remove unnecessary methods from swagger processing; Fix path errors.
1 parent 7382a3f commit ba6bb95

File tree

3 files changed

+80
-190
lines changed

3 files changed

+80
-190
lines changed

extensions/sdk-assistant/_prompt.xml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
This file is a merged representation of the entire codebase, combining all repository files into a single document.
2-
Generated by Repomix on: 2025-01-22T22:04:54.185Z
2+
Generated by Repomix on: 2025-01-28T17:21:45.629Z
33

44
<file_summary>
55
This section contains a summary of this file.
@@ -918,6 +918,15 @@ def update_dict_values(obj: dict[str, Any], /, **kwargs: Any) -> None:
918918
* https://github.com/posit-dev/posit-sdk-py/pull/366#discussion_r1887845267
919919
"""
920920
...
921+
922+
def is_local() -> bool:
923+
"""Returns true if called from a piece of content running on a Connect server.
924+
925+
The connect server will always set the environment variable `RSTUDIO_PRODUCT=CONNECT`.
926+
We can use this environment variable to determine if the content is running locally
927+
or on a Connect server.
928+
"""
929+
...
921930
</file>
922931

923932
<file path="connect/auth.pyi">
@@ -1252,12 +1261,14 @@ class Client(ContextManager):
12521261
"""
12531262
...
12541263

1255-
@requires("2025.01.0-dev")
1264+
@requires("2025.01.0")
12561265
def with_user_session_token(self, token: str) -> Client:
12571266
"""Create a new Client scoped to the user specified in the user session token.
12581267

12591268
Create a new Client instance from a user session token exchange for an api key scoped to the
1260-
user specified in the token.
1269+
user specified in the token (the user viewing your app). If running your application locally,
1270+
a user session token will not exist, which will cause this method to result in an error needing
1271+
to be handled in your application.
12611272

12621273
Parameters
12631274
----------

extensions/sdk-assistant/uv_update_prompt.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ def cleanup() -> None:
3333
path = here / f
3434
if path.exists():
3535
print("Removing path:", path.relative_to(here))
36-
shutil.rmtree(path)
36+
if path.is_file():
37+
path.unlink()
38+
else:
39+
shutil.rmtree(path)
3740
print("--\n")
3841

3942

extensions/sdk-assistant/uv_update_swagger.py

Lines changed: 62 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -6,164 +6,94 @@
66
# ///
77
from __future__ import annotations
88

9-
from copy import deepcopy
109
import pathlib
1110
import os
1211
import json
13-
from typing_extensions import Any, NotRequired, TypedDict, TypeVar
12+
from typing_extensions import TypedDict, TypeVar
1413

1514
here = pathlib.Path(__file__).parent
1615
os.chdir(here)
1716

1817
T = 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-
7520
class 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

8424
class 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\nAPI 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

Comments
 (0)