Skip to content

Commit 385bc88

Browse files
[Confluence] ConfluenceCloud: add get_page_id (#1704)
docs/confluence.rst documents get_page_id(space, title, type="page"), and the Server and ConfluenceV2 clients have it, but the Cloud client does not, so Confluence(url="https://<site>.atlassian.net") and ConfluenceCloud raise AttributeError for it. Add get_page_id to Cloud. It reuses the V2 lookup from page_exists (space key -> space-id, then api/v2/pages or api/v2/blogposts for type="blogpost") and returns the ID of the first match, or None.
1 parent 2df0553 commit 385bc88

2 files changed

Lines changed: 76 additions & 10 deletions

File tree

‎atlassian/confluence/cloud/__init__.py‎

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -244,23 +244,22 @@ def blog_post_exists(self, space_key, title, **kwargs):
244244
result = self.get_blog_post_by_title(space_key, title, **kwargs)
245245
return len(result.get("results", [])) > 0
246246

247-
def page_exists(self, space_key, title, type=None, **kwargs):
248-
"""Check whether a page exists using the Cloud V2 page endpoint.
247+
def _get_v2_content_by_title(self, space_key, title, type=None, **kwargs):
248+
"""Return the V2 pages (or blog posts) matching a space key and title.
249249
250-
The retired V1 content lookup accepted a space key directly. V2 page
251-
queries require a space ID, so resolve the key first and request only
252-
one matching page. Pass type="blogpost" to check the V2 blog post
253-
endpoint instead.
250+
V2 page queries require a space ID, so resolve the key first and
251+
request only one match. Pass type="blogpost" to query the V2 blog post
252+
endpoint instead. Returns an empty list when nothing matches.
254253
"""
255254
try:
256255
spaces = self.get("api/v2/spaces", params={"keys": [space_key], "limit": 1})
257256
except HTTPError as error:
258257
if error.response is not None and error.response.status_code == 404:
259-
return False
258+
return []
260259
raise
261260
space_results = spaces.get("results", [])
262261
if not space_results:
263-
return False
262+
return []
264263

265264
try:
266265
result = self.get(
@@ -276,9 +275,30 @@ def page_exists(self, space_key, title, type=None, **kwargs):
276275
)
277276
except HTTPError as error:
278277
if error.response is not None and error.response.status_code == 404:
279-
return False
278+
return []
280279
raise
281-
return bool(result.get("results", []))
280+
return result.get("results", [])
281+
282+
def page_exists(self, space_key, title, type=None, **kwargs):
283+
"""Check whether a page exists using the Cloud V2 page endpoint.
284+
285+
The retired V1 content lookup accepted a space key directly. V2 page
286+
queries require a space ID, so resolve the key first and request only
287+
one matching page. Pass type="blogpost" to check the V2 blog post
288+
endpoint instead.
289+
"""
290+
return bool(self._get_v2_content_by_title(space_key, title, type=type, **kwargs))
291+
292+
def get_page_id(self, space, title, type="page"):
293+
"""Get a page ID by space key and title using the Cloud V2 API.
294+
295+
Pass type="blogpost" to look up a blog post instead of a page.
296+
Returns the ID of the first match, or None if there is none.
297+
"""
298+
results = self._get_v2_content_by_title(space, title, type=type)
299+
if results:
300+
return str(results[0]["id"])
301+
return None
282302

283303
def get_page_child_by_type(self, page_id, type="page", start=None, limit=None, expand=None):
284304
"""

‎tests/confluence/test_confluence_cloud.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,52 @@ def test_page_exists_returns_false_for_unknown_space(self, mock_get, confluence_
247247
assert confluence_cloud.page_exists("MISSING", "Test Page") is False
248248
mock_get.assert_called_once_with("api/v2/spaces", params={"keys": ["MISSING"], "limit": 1})
249249

250+
@patch.object(ConfluenceCloud, "get")
251+
def test_get_page_id_uses_v2_space_and_page_endpoints(self, mock_get, confluence_cloud):
252+
mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": [{"id": "123"}]}]
253+
254+
assert confluence_cloud.get_page_id("TEST", "Test Page") == "123"
255+
assert mock_get.call_args_list[0].args == ("api/v2/spaces",)
256+
assert mock_get.call_args_list[0].kwargs == {"params": {"keys": ["TEST"], "limit": 1}}
257+
assert mock_get.call_args_list[1].args == ("api/v2/pages",)
258+
assert mock_get.call_args_list[1].kwargs == {
259+
"params": {
260+
"space-id": "42",
261+
"title": "Test Page",
262+
"status": "current",
263+
"body-format": "none",
264+
"limit": 1,
265+
}
266+
}
267+
268+
@patch.object(ConfluenceCloud, "get")
269+
def test_get_page_id_uses_v2_blogpost_endpoint_for_blogpost_type(self, mock_get, confluence_cloud):
270+
mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": [{"id": "456"}]}]
271+
272+
assert confluence_cloud.get_page_id("TEST", "Release notes", type="blogpost") == "456"
273+
assert mock_get.call_args_list[1].args == ("api/v2/blogposts",)
274+
assert mock_get.call_args_list[1].kwargs["params"]["title"] == "Release notes"
275+
276+
@patch.object(ConfluenceCloud, "get")
277+
def test_get_page_id_returns_none_when_not_found(self, mock_get, confluence_cloud):
278+
mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": []}]
279+
280+
assert confluence_cloud.get_page_id("TEST", "Missing") is None
281+
282+
@patch.object(ConfluenceCloud, "get")
283+
def test_get_page_id_returns_none_for_unknown_space(self, mock_get, confluence_cloud):
284+
mock_get.return_value = {"results": []}
285+
286+
assert confluence_cloud.get_page_id("MISSING", "Test Page") is None
287+
mock_get.assert_called_once_with("api/v2/spaces", params={"keys": ["MISSING"], "limit": 1})
288+
289+
@patch.object(ConfluenceCloud, "get")
290+
def test_get_page_id_through_confluence_factory(self, mock_get):
291+
mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": [{"id": "123"}]}]
292+
confluence = Confluence(url="https://test.atlassian.net", username="user", password="api-token")
293+
294+
assert confluence.get_page_id("TEST", "Test Page") == "123"
295+
250296
@patch.object(ConfluenceCloud, "get")
251297
def test_get_all_blog_posts_from_space(self, mock_get, confluence_cloud):
252298
"""Test get_all_blog_posts_from_space method."""

0 commit comments

Comments
 (0)