From 2d50288524a5f44bca903d4c36696d73ed0571b6 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Fri, 5 Dec 2025 13:24:44 -0500 Subject: [PATCH 1/2] Switch to v1/user --- rsconnect/api.py | 2 +- rsconnect/models.py | 2 ++ tests/test_main.py | 4 ++-- tests/test_main_content.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rsconnect/api.py b/rsconnect/api.py index 1c5817ef..877d095b 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -399,7 +399,7 @@ def _tweak_response(self, response: HTTPResponse) -> JsonData | HTTPResponse: ) def me(self) -> UserRecord: - response = cast(Union[UserRecord, HTTPResponse], self.get("me")) + response = cast(Union[UserRecord, HTTPResponse], self.get("v1/user")) response = self._server.handle_bad_response(response) return response diff --git a/rsconnect/models.py b/rsconnect/models.py index 44bd97f1..49cea38d 100644 --- a/rsconnect/models.py +++ b/rsconnect/models.py @@ -601,6 +601,7 @@ class UserRecord(TypedDict): first_name: str last_name: str password: str + user_role: str created_time: str updated_time: str active_time: str | None @@ -608,3 +609,4 @@ class UserRecord(TypedDict): locked: bool guid: str preferences: dict[str, object] + privileges: list[str] diff --git a/tests/test_main.py b/tests/test_main.py index dfaa908a..dd3410c1 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -127,8 +127,8 @@ def test_deploy_draft(self, command, target, expected_activate, caplog): ) httpretty.register_uri( httpretty.GET, - "http://fake_server/__api__/me", - body=open("tests/testdata/rstudio-responses/get-user.json", "r").read(), + "http://fake_server/__api__/v1/user", + body=open("tests/testdata/connect-responses/me.json", "r").read(), adding_headers={"Content-Type": "application/json"}, status=200, ) diff --git a/tests/test_main_content.py b/tests/test_main_content.py index 64984dac..db5ba6be 100644 --- a/tests/test_main_content.py +++ b/tests/test_main_content.py @@ -55,7 +55,7 @@ def register_content_endpoints(i: int, guid: str): ) httpretty.register_uri( httpretty.GET, - f"{connect_server}/__api__/me", + f"{connect_server}/__api__/v1/user", body=open("tests/testdata/connect-responses/me.json", "r").read(), adding_headers={"Content-Type": "application/json"}, ) From 5a18a5f69b99ae4507197d87c6f5274a9ff8ad64 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Fri, 5 Dec 2025 14:04:11 -0500 Subject: [PATCH 2/2] Renamings --- rsconnect/api.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/rsconnect/api.py b/rsconnect/api.py index 877d095b..a1be3c75 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -426,22 +426,22 @@ def app_get(self, app_id: str) -> ContentItemV0: response = self._server.handle_bad_response(response) return response - def app_add_environment_vars(self, app_guid: str, env_vars: list[tuple[str, str]]): + def add_environment_vars(self, content_guid: str, env_vars: list[tuple[str, str]]): env_body = [dict(name=kv[0], value=kv[1]) for kv in env_vars] - return self.patch("v1/content/%s/environment" % app_guid, body=env_body) + return self.patch("v1/content/%s/environment" % content_guid, body=env_body) - def is_app_failed_response(self, response: HTTPResponse | JsonData) -> bool: + def is_failed_response(self, response: HTTPResponse | JsonData) -> bool: return isinstance(response, HTTPResponse) and response.status >= 500 - def app_access(self, app_guid: str) -> None: + def access_content(self, content_guid: str) -> None: method = "GET" base = dirname(self._url.path) # remove __api__ - path = f"{base}/content/{app_guid}/" + path = f"{base}/content/{content_guid}/" response = self._do_request(method, path, None, None, 3, {}, False) - if self.is_app_failed_response(response): + if self.is_failed_response(response): # Get content metadata to construct logs URL - content = self.content_get(app_guid) + content = self.content_get(content_guid) logs_url = content["dashboard_url"] + "/logs" raise RSConnectException( "Could not access the deployed content. " @@ -467,7 +467,7 @@ def content_get(self, content_guid: str) -> ContentItemV1: response = self._server.handle_bad_response(response) return response - def get_content_by_id(self, app_id: str) -> ContentItemV1: + def get_content_by_id(self, id: str) -> ContentItemV1: """ Get content by ID, which can be either a numeric ID (legacy) or GUID. @@ -475,12 +475,12 @@ def get_content_by_id(self, app_id: str) -> ContentItemV1: :return: ContentItemV1 data """ # Check if it looks like a GUID (contains hyphens) - if "-" in str(app_id): - return self.content_get(app_id) + if "-" in str(id): + return self.content_get(id) else: # Legacy numeric ID - get v0 content first to get GUID - # TODO: deprecation warning - app_v0 = self.app_get(app_id) + app_v0 = self.app_get(id) + # TODO: deprecation warning here return self.content_get(app_v0["guid"]) def content_create(self, name: str) -> ContentItemV1: @@ -516,7 +516,9 @@ def content_build( response = self._server.handle_bad_response(response) return response - def content_deploy(self, app_guid: str, bundle_id: Optional[str] = None, activate: bool = True) -> BuildOutputDTO: + def content_deploy( + self, content_guid: str, bundle_id: Optional[str] = None, activate: bool = True + ) -> BuildOutputDTO: body: dict[str, str | bool | None] = {"bundle_id": bundle_id} if not activate: # The default behavior is to activate the app after deploying. @@ -525,7 +527,7 @@ def content_deploy(self, app_guid: str, bundle_id: Optional[str] = None, activat body["activate"] = False response = cast( Union[BuildOutputDTO, HTTPResponse], - self.post("v1/content/%s/deploy" % app_guid, body=body), + self.post("v1/content/%s/deploy" % content_guid, body=body), ) response = self._server.handle_bad_response(response) return response @@ -590,7 +592,7 @@ def deploy( app_guid = app["guid"] if env_vars: - result = self.app_add_environment_vars(app_guid, list(env_vars.items())) + result = self.add_environment_vars(app_guid, list(env_vars.items())) result = self._server.handle_bad_response(result) if app["title"] != app_title and not title_is_default: @@ -683,10 +685,6 @@ def output_task_log( log_callback(line) -# for backwards compatibility with rsconnect-jupyter -RSConnect = RSConnectClient - - class ServerDetailsPython(TypedDict): api_enabled: bool versions: list[str] @@ -1175,7 +1173,7 @@ def verify_deployment(self): raise RSConnectException("To verify deployment, client must be a RSConnectClient.") deployed_info = self.deployed_info app_guid = deployed_info["app_guid"] - self.client.app_access(app_guid) + self.client.access_content(app_guid) @cls_logged("Validating app mode...") def validate_app_mode(self, app_mode: AppMode):