From 06ca4d6b7f9075675bd390dcef9e50f7518d46db Mon Sep 17 00:00:00 2001 From: Christopher Graham Date: Wed, 18 Jun 2025 17:15:20 -0400 Subject: [PATCH 1/6] Extend content.get() to support includes param --- .../tests/posit/connect/test_content.py | 8 +++ src/posit/connect/content.py | 22 +++++++- tests/posit/connect/test_content.py | 52 ++++++++++++------- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/integration/tests/posit/connect/test_content.py b/integration/tests/posit/connect/test_content.py index 4b555824..b50b4e2b 100644 --- a/integration/tests/posit/connect/test_content.py +++ b/integration/tests/posit/connect/test_content.py @@ -25,6 +25,14 @@ def test_count(self): def test_get(self): assert self.client.content.get(self.content["guid"]) == self.content + def test_get_with_include_string(self): + assert self.client.content.get(self.content["guid"], include="owner") + + def test_get_with_include_list(self): + assert self.client.content.get( + self.content["guid"], include=["owner", "tags", "vanity_url"] + ) + def test_find(self): assert self.client.content.find() diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py index 16f8bfa6..afce16c8 100644 --- a/src/posit/connect/content.py +++ b/src/posit/connect/content.py @@ -983,16 +983,34 @@ def find_one(self, **conditions) -> Optional[ContentItem]: items = self.find(**conditions) return next(iter(items), None) - def get(self, guid: str) -> ContentItem: + def get( + self, + guid: str, + *, + include: Optional[ + Literal["owner", "tags", "vanity_url"] | list[Literal["owner", "tags", "vanity_url"]] + ] = None, + ) -> ContentItem: """Get a content item. Parameters ---------- guid : str + The unique identifier of the content item. + include : str or list of str, optional + Additional details to include in the response. + Allowed values: 'owner', 'tags', 'vanity_url'. Returns ------- ContentItem """ - response = self._ctx.client.get(f"v1/content/{guid}") + params = {} + if include is not None: + if isinstance(include, list): + params["include"] = ",".join(include) + else: + params["include"] = include + + response = self._ctx.client.get(f"v1/content/{guid}", params=params) return ContentItem(self._ctx, **response.json()) diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index 310582b7..cbe1c736 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -169,7 +169,7 @@ def test(self): assert content[2]["name"] == "My-Streamlit-app" @responses.activate - def test_params_include(self): + def test_params_include_string(self): # behavior mock_get = responses.get( "https://connect.example/__api__/v1/content", @@ -204,24 +204,6 @@ def test_params_include_list(self): # assert assert mock_get.call_count == 1 - @responses.activate - def test_params_include_none(self): - # behavior - mock_get = responses.get( - "https://connect.example/__api__/v1/content", - json=load_mock("v1/content.json"), - match=[matchers.query_param_matcher({})], - ) - - # setup - client = Client("https://connect.example", "12345") - - # invoke - client.content.find(include=None) - - # assert - assert mock_get.call_count == 1 - class TestContentsFindBy: @responses.activate @@ -373,6 +355,38 @@ def test(self): get_one = con.content.get("f2f37341-e21d-3d80-c698-a935ad614066") assert get_one["name"] == "Performance-Data-1671216053560" + @responses.activate + def test_get_with_include_string(self): + guid = "f2f37341-e21d-3d80-c698-a935ad614066" + mock_get = responses.get( + f"https://connect.example/__api__/v1/content/{guid}", + json=load_mock(f"v1/content/{guid}.json"), + match=[matchers.query_param_matcher({"include": "owner"})], + ) + + con = Client("https://connect.example", "12345") + content = con.content.get(guid, include="owner") + assert content["guid"] == guid + + # assert + assert mock_get.call_count == 1 + + + @responses.activate + def test_get_with_include_list(self): + guid = "f2f37341-e21d-3d80-c698-a935ad614066" + mock_get = responses.get( + f"https://connect.example/__api__/v1/content/{guid}", + json=load_mock(f"v1/content/{guid}.json"), + match=[matchers.query_param_matcher({"include": "owner,tags,vanity_url"})], + ) + + con = Client("https://connect.example", "12345") + content = con.content.get(guid, include=["owner", "tags", "vanity_url"]) + assert content["guid"] == guid + + # assert + assert mock_get.call_count == 1 class TestContentsCount: @responses.activate From 163db090ac60463830e962de7eef05d4cee1b6b4 Mon Sep 17 00:00:00 2001 From: Christopher Graham Date: Tue, 24 Jun 2025 13:52:27 -0400 Subject: [PATCH 2/6] Restore accidentally deleted test from TestContentsFind --- tests/posit/connect/test_content.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index cbe1c736..5e6c44ee 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -204,6 +204,23 @@ def test_params_include_list(self): # assert assert mock_get.call_count == 1 + @responses.activate + def test_params_include_none(self): + # behavior + mock_get = responses.get( + "https://connect.example/__api__/v1/content", + json=load_mock("v1/content.json"), + match=[matchers.query_param_matcher({})], + ) + + # setup + client = Client("https://connect.example", "12345") + + # invoke + client.content.find(include=None) + + # assert + assert mock_get.call_count == 1 class TestContentsFindBy: @responses.activate From 678a9cb9c77eed45736753734294589dd3de54e6 Mon Sep 17 00:00:00 2001 From: Christopher Graham Date: Tue, 24 Jun 2025 14:06:13 -0400 Subject: [PATCH 3/6] Add GET test for include=None --- tests/posit/connect/test_content.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index 5e6c44ee..62f376a6 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -405,6 +405,23 @@ def test_get_with_include_list(self): # assert assert mock_get.call_count == 1 + @responses.activate + def test_get_with_include_none(self): + guid = "f2f37341-e21d-3d80-c698-a935ad614066" + mock_get = responses.get( + f"https://connect.example/__api__/v1/content/{guid}", + json=load_mock(f"v1/content/{guid}.json"), + match=[matchers.query_param_matcher({})], + ) + + con = Client("https://connect.example", "12345") + content = con.content.get(guid, include=None) + assert content["guid"] == guid + + # assert + assert mock_get.call_count == 1 + + class TestContentsCount: @responses.activate def test(self): From da3ac8e3aab296ad48db59883776b1d201a5dba9 Mon Sep 17 00:00:00 2001 From: Christopher Graham Date: Tue, 24 Jun 2025 14:08:52 -0400 Subject: [PATCH 4/6] Rename tests for consistency --- tests/posit/connect/test_content.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index 62f376a6..ca19abe7 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -325,7 +325,7 @@ def test_name(self): assert content_item["name"] == name @responses.activate - def test_params_include(self): + def test_params_include_string(self): # behavior mock_get = responses.get( "https://connect.example/__api__/v1/content", @@ -373,7 +373,7 @@ def test(self): assert get_one["name"] == "Performance-Data-1671216053560" @responses.activate - def test_get_with_include_string(self): + def test_params_include_string(self): guid = "f2f37341-e21d-3d80-c698-a935ad614066" mock_get = responses.get( f"https://connect.example/__api__/v1/content/{guid}", @@ -390,7 +390,7 @@ def test_get_with_include_string(self): @responses.activate - def test_get_with_include_list(self): + def test_params_include_list(self): guid = "f2f37341-e21d-3d80-c698-a935ad614066" mock_get = responses.get( f"https://connect.example/__api__/v1/content/{guid}", @@ -406,7 +406,7 @@ def test_get_with_include_list(self): assert mock_get.call_count == 1 @responses.activate - def test_get_with_include_none(self): + def test_params_include_none(self): guid = "f2f37341-e21d-3d80-c698-a935ad614066" mock_get = responses.get( f"https://connect.example/__api__/v1/content/{guid}", From 987662d6f331b109469b3b381986c2687b936337 Mon Sep 17 00:00:00 2001 From: Christopher Graham Date: Tue, 24 Jun 2025 14:09:32 -0400 Subject: [PATCH 5/6] Format --- integration/tests/posit/connect/test_content.py | 2 +- tests/posit/connect/test_content.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/tests/posit/connect/test_content.py b/integration/tests/posit/connect/test_content.py index b50b4e2b..2fd8c1e6 100644 --- a/integration/tests/posit/connect/test_content.py +++ b/integration/tests/posit/connect/test_content.py @@ -26,7 +26,7 @@ def test_get(self): assert self.client.content.get(self.content["guid"]) == self.content def test_get_with_include_string(self): - assert self.client.content.get(self.content["guid"], include="owner") + assert self.client.content.get(self.content["guid"], include="owner") def test_get_with_include_list(self): assert self.client.content.get( diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index ca19abe7..4f86feb2 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -222,6 +222,7 @@ def test_params_include_none(self): # assert assert mock_get.call_count == 1 + class TestContentsFindBy: @responses.activate def test(self): @@ -388,7 +389,6 @@ def test_params_include_string(self): # assert assert mock_get.call_count == 1 - @responses.activate def test_params_include_list(self): guid = "f2f37341-e21d-3d80-c698-a935ad614066" From 9d27ff7cb75231ce6d1b548100da7c705e3aa55f Mon Sep 17 00:00:00 2001 From: Christopher Graham Date: Wed, 25 Jun 2025 17:25:00 -0400 Subject: [PATCH 6/6] Refactor so get() is hard coded to always use all `include` fields --- .../tests/posit/connect/test_content.py | 20 ++++---- src/posit/connect/content.py | 20 ++------ tests/posit/connect/test_content.py | 50 +------------------ 3 files changed, 16 insertions(+), 74 deletions(-) diff --git a/integration/tests/posit/connect/test_content.py b/integration/tests/posit/connect/test_content.py index 2fd8c1e6..adcca083 100644 --- a/integration/tests/posit/connect/test_content.py +++ b/integration/tests/posit/connect/test_content.py @@ -23,15 +23,17 @@ def test_count(self): assert self.client.content.count() == 1 def test_get(self): - assert self.client.content.get(self.content["guid"]) == self.content - - def test_get_with_include_string(self): - assert self.client.content.get(self.content["guid"], include="owner") - - def test_get_with_include_list(self): - assert self.client.content.get( - self.content["guid"], include=["owner", "tags", "vanity_url"] - ) + item = self.client.content.get(self.content["guid"]) + # get() always includes owner, tags, and vanity_url. Owner data is always present in all + # content, tags and vanity_url is only present if explicitly set in the content. + # Check that essential fields match instead of exact equality + for key in self.content: + assert key in item + assert item[key] == self.content[key] + # Also verify we have the additional fields that should always be included + assert "owner" in item + assert "tags" not in item + assert "vanity_url" not in item def test_find(self): assert self.client.content.find() diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py index afce16c8..7a2b6224 100644 --- a/src/posit/connect/content.py +++ b/src/posit/connect/content.py @@ -983,34 +983,20 @@ def find_one(self, **conditions) -> Optional[ContentItem]: items = self.find(**conditions) return next(iter(items), None) - def get( - self, - guid: str, - *, - include: Optional[ - Literal["owner", "tags", "vanity_url"] | list[Literal["owner", "tags", "vanity_url"]] - ] = None, - ) -> ContentItem: + def get(self, guid: str) -> ContentItem: """Get a content item. Parameters ---------- guid : str The unique identifier of the content item. - include : str or list of str, optional - Additional details to include in the response. - Allowed values: 'owner', 'tags', 'vanity_url'. Returns ------- ContentItem """ - params = {} - if include is not None: - if isinstance(include, list): - params["include"] = ",".join(include) - else: - params["include"] = include + # Always request all available optional fields for the content item + params = {"include": "owner,tags,vanity_url"} response = self._ctx.client.get(f"v1/content/{guid}", params=params) return ContentItem(self._ctx, **response.json()) diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index 4f86feb2..9d146887 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -365,62 +365,16 @@ def test_params_include_none(self): class TestContentsGet: @responses.activate def test(self): + # All calls to get() should automatically include all available optional fields responses.get( "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066", json=load_mock("v1/content/f2f37341-e21d-3d80-c698-a935ad614066.json"), + match=[matchers.query_param_matcher({"include": "owner,tags,vanity_url"})], ) con = Client("https://connect.example", "12345") get_one = con.content.get("f2f37341-e21d-3d80-c698-a935ad614066") assert get_one["name"] == "Performance-Data-1671216053560" - @responses.activate - def test_params_include_string(self): - guid = "f2f37341-e21d-3d80-c698-a935ad614066" - mock_get = responses.get( - f"https://connect.example/__api__/v1/content/{guid}", - json=load_mock(f"v1/content/{guid}.json"), - match=[matchers.query_param_matcher({"include": "owner"})], - ) - - con = Client("https://connect.example", "12345") - content = con.content.get(guid, include="owner") - assert content["guid"] == guid - - # assert - assert mock_get.call_count == 1 - - @responses.activate - def test_params_include_list(self): - guid = "f2f37341-e21d-3d80-c698-a935ad614066" - mock_get = responses.get( - f"https://connect.example/__api__/v1/content/{guid}", - json=load_mock(f"v1/content/{guid}.json"), - match=[matchers.query_param_matcher({"include": "owner,tags,vanity_url"})], - ) - - con = Client("https://connect.example", "12345") - content = con.content.get(guid, include=["owner", "tags", "vanity_url"]) - assert content["guid"] == guid - - # assert - assert mock_get.call_count == 1 - - @responses.activate - def test_params_include_none(self): - guid = "f2f37341-e21d-3d80-c698-a935ad614066" - mock_get = responses.get( - f"https://connect.example/__api__/v1/content/{guid}", - json=load_mock(f"v1/content/{guid}.json"), - match=[matchers.query_param_matcher({})], - ) - - con = Client("https://connect.example", "12345") - content = con.content.get(guid, include=None) - assert content["guid"] == guid - - # assert - assert mock_get.call_count == 1 - class TestContentsCount: @responses.activate