Skip to content

Commit 45a1998

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

File tree

3 files changed

+66
-178
lines changed

3 files changed

+66
-178
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: 48 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# ///
77
from __future__ import annotations
88

9-
from copy import deepcopy
109
import pathlib
1110
import os
1211
import json
@@ -18,177 +17,23 @@
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]]
89-
90-
91-
def expand_all_references(document: SwaggerDocument) -> SwaggerDocument:
92-
"""
93-
Expands all JSON references.
94-
95-
Expands all references ($ref) in the merged swagger document by replacing them with
96-
their full definitions.
97-
98-
This returns a new document with all references expanded.
99-
100-
Arguments
101-
---------
102-
document
103-
The dictionary representing the Swagger document to process
104-
105-
Returns
106-
-------
107-
:
108-
The processed Swagger document with all references expanded.
109-
"""
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-
)
134-
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"]
147-
)
148-
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
155-
)
156-
157-
# Expand refs in definitions
158-
if "definitions" in ret_document:
159-
ret_document["definitions"] = expand_refs(
160-
ret_document, ret_document["definitions"]
161-
)
162-
163-
return ret_document
164-
165-
166-
def require_swagger():
167-
if not (here / "_swagger.json").exists():
168-
import urllib.request
169-
170-
urllib.request.urlretrieve(
171-
"https://docs.posit.co/connect/api/swagger.json",
172-
here / "_swagger.json",
173-
)
174-
175-
doc = json.load(here / "_swagger.json")
176-
177-
swagger = expand_all_references(doc)
178-
return swagger
25+
paths: dict[str, SwaggerOperation]
17926

18027

18128
class OperationDef(TypedDict):
182-
name: str
183-
tags: list[str]
18429
method: str
18530
route: str
186-
definition: dict[str, Any]
31+
summary: str
18732

18833

18934
def transform_swagger_to_operation_dict(
19035
swagger_dict: SwaggerDocument,
191-
) -> dict[str, OperationDef]:
36+
) -> list[OperationDef]:
19237
"""
19338
Swagger to operation dictionary transformation.
19439
@@ -204,49 +49,78 @@ def transform_swagger_to_operation_dict(
20449
:
20550
A dictionary where each key is an operation ID and the value is the operation definition.
20651
"""
207-
operation_dict = {}
52+
operation_list: list[OperationDef] = []
53+
54+
if "paths" not in swagger_dict:
55+
raise ValueError(
56+
"The Swagger document `swagger_dict=` does not contain a 'paths' key."
57+
)
20858

20959
if "paths" in swagger_dict:
21060
for route, operations in swagger_dict["paths"].items():
61+
if not isinstance(route, str):
62+
raise ValueError(
63+
f"Expected route to be a string, but got {type(route)}."
64+
)
21165
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+
)
21270
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,
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+
)
78+
summary = operation["summary"]
79+
if not isinstance(summary, str):
80+
raise ValueError(
81+
f"Expected summary to be a string, but got {type(summary)}."
82+
)
83+
operation_list.append(
84+
{
22285
"method": method,
22386
"route": route,
224-
"definition": operation,
87+
"summary": summary,
22588
}
89+
)
22690

227-
return operation_dict
91+
return operation_list
22892

22993

23094
def main():
231-
swagger = require_swagger()
95+
if not (here / "_swagger.json").exists():
96+
import urllib.request
97+
98+
urllib.request.urlretrieve(
99+
"https://docs.posit.co/connect/api/swagger.json",
100+
here / "_swagger.json",
101+
)
102+
103+
swagger = json.loads((here / "_swagger.json").read_text())
232104

233105
operations = transform_swagger_to_operation_dict(swagger)
234106

107+
# Write out the swagger portion of the instructions with a preamble and a
108+
# list of all the API routes and their short summaries.
235109
with open(here / "_swagger_prompt.md", "w") as f:
236110
f.write(
237111
"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"
238112
""
239113
)
240114

241-
for operation in operations.values():
242-
# `"GET /v1/tasks/{id} Get task details"`
115+
for operation in operations:
116+
# `"* GET /v1/tasks/{id} Get task details"`
243117
f.write(
244118
"* "
245119
+ operation["method"].upper()
246120
+ " "
247121
+ operation["route"]
248122
+ " "
249-
+ operation["definition"]["summary"].replace("\n", " ").strip()
123+
+ operation["summary"].replace("\n", " ").strip()
250124
+ "\n",
251125
)
252126

0 commit comments

Comments
 (0)