Skip to content

Commit 1af094d

Browse files
committed
Add custom content label and property methods, fix compatibility layer initialization
1 parent d15454d commit 1af094d

File tree

2 files changed

+215
-21
lines changed

2 files changed

+215
-21
lines changed

atlassian/confluence/cloud/cloud.py

Lines changed: 212 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
"""
55
Confluence Cloud API implementation
66
"""
7+
import functools
8+
import json
79
import logging
10+
import re
811
import warnings
912
from typing import Any, Dict, List, Optional, Tuple, Union
1013

@@ -31,6 +34,14 @@ def __init__(self, url: str, *args, **kwargs):
3134
kwargs.setdefault("api_version", 2)
3235
super().__init__(url, *args, **kwargs)
3336

37+
# Initialize the compatibility method mapping
38+
self._compatibility_method_mapping = {}
39+
40+
# Add compatibility mapping here if needed
41+
# self._compatibility_method_mapping = {
42+
# "old_method_name": "new_method_name"
43+
# }
44+
3445
# Warn about V1 method usage
3546
warnings.warn(
3647
"V1 methods are deprecated in ConfluenceCloud. Use V2 methods instead.", DeprecationWarning, stacklevel=2
@@ -49,7 +60,7 @@ def __getattr__(self, name):
4960
Raises:
5061
AttributeError: If no mapping exists and the attribute isn't found
5162
"""
52-
if name in self._compatibility_method_mapping:
63+
if hasattr(self, "_compatibility_method_mapping") and name in self._compatibility_method_mapping:
5364
v2_method_name = self._compatibility_method_mapping[name]
5465
v2_method = getattr(self, v2_method_name)
5566

@@ -840,37 +851,35 @@ def get_page_property_by_key(self, page_id: str, property_key: str) -> Dict[str,
840851
log.error(f"Failed to retrieve property {property_key} for page {page_id}: {e}")
841852
raise
842853

843-
def create_page_property(self, page_id: str, property_key: str, property_value: Any) -> Dict[str, Any]:
854+
def create_page_property(self, page_id: str, key: str, value: Any) -> Dict[str, Any]:
844855
"""
845856
Creates a new property for a page.
846857
847858
Args:
848859
page_id: The ID of the page
849-
property_key: The key of the property to create. Must only contain alphanumeric
850-
characters and periods
851-
property_value: The value of the property. Can be any JSON-serializable value
860+
key: The key of the property to create. Must only contain alphanumeric
861+
characters and periods
862+
value: The value of the property. Can be any JSON-serializable value
852863
853864
Returns:
854-
The created page property object
865+
The created property object
855866
856867
Raises:
857868
HTTPError: If the API call fails
858-
ValueError: If the property_key has invalid characters
869+
ValueError: If the key has invalid characters
859870
"""
860871
# Validate key format
861-
import re
862-
863-
if not re.match(r"^[a-zA-Z0-9.]+$", property_key):
872+
if not re.match(r"^[a-zA-Z0-9.]+$", key):
864873
raise ValueError("Property key must only contain alphanumeric characters and periods.")
865874

866875
endpoint = self.get_endpoint("page_properties", id=page_id)
867876

868-
data = {"key": property_key, "value": property_value}
877+
data = {"key": key, "value": value}
869878

870879
try:
871880
return self.post(endpoint, data=data)
872881
except Exception as e:
873-
log.error(f"Failed to create property {property_key} for page {page_id}: {e}")
882+
log.error(f"Failed to create property {key} for page {page_id}: {e}")
874883
raise
875884

876885
def update_page_property(
@@ -2121,7 +2130,7 @@ def get_custom_content(
21212130
page_id: (optional) Filter by page ID
21222131
blog_post_id: (optional) Filter by blog post ID
21232132
custom_content_id: (optional) Filter by parent custom content ID
2124-
id: (optional) List of custom content IDs to filter by
2133+
ids: (optional) List of custom content IDs to filter by
21252134
status: (optional) Filter by status. Valid values: "current", "draft", "archived", "trashed", "deleted", "any"
21262135
body_format: (optional) Format to retrieve the body in.
21272136
Valid values: "storage", "atlas_doc_format", "raw", "view"
@@ -2151,19 +2160,203 @@ def get_custom_content(
21512160
if ids:
21522161
params["id"] = ",".join(ids)
21532162
if status:
2154-
params["id"] = ",".join(ids)
2163+
params["status"] = status
2164+
if body_format:
2165+
params["body-format"] = body_format
2166+
if sort:
2167+
params["sort"] = sort
2168+
if limit:
2169+
params["limit"] = limit
2170+
if cursor:
2171+
params["cursor"] = cursor
21552172

2156-
if key:
2157-
params["key"] = ",".join(key)
2173+
try:
2174+
return list(self._get_paged(endpoint, params=params))
2175+
except Exception as e:
2176+
log.error(f"Failed to retrieve custom content: {e}")
2177+
raise
21582178

2159-
if space_id:
2160-
params["spaceId"] = space_id
2179+
def add_custom_content_label(self, custom_content_id: str, label: str, prefix: str = "global") -> Dict[str, Any]:
2180+
"""
2181+
Adds a label to custom content.
2182+
2183+
Args:
2184+
custom_content_id: The ID of the custom content
2185+
label: The label to add
2186+
prefix: (optional) The prefix of the label. Default is "global"
2187+
2188+
Returns:
2189+
The created label object
2190+
2191+
Raises:
2192+
HTTPError: If the API call fails
2193+
ValueError: If the label is invalid
2194+
"""
2195+
if not label:
2196+
raise ValueError("Label cannot be empty")
2197+
2198+
endpoint = self.get_endpoint("custom_content_labels", id=custom_content_id)
2199+
2200+
data = {"name": label, "prefix": prefix}
2201+
2202+
try:
2203+
return self.post(endpoint, data=data)
2204+
except Exception as e:
2205+
log.error(f"Failed to add label '{label}' to custom content {custom_content_id}: {e}")
2206+
raise
2207+
2208+
def delete_custom_content_label(self, custom_content_id: str, label: str, prefix: str = "global") -> bool:
2209+
"""
2210+
Delete a label from custom content.
2211+
2212+
Args:
2213+
custom_content_id: The ID of the custom content
2214+
label: The label to delete
2215+
prefix: (optional) The prefix of the label. Default is "global"
2216+
2217+
Returns:
2218+
True if the label was successfully deleted, False otherwise
2219+
2220+
Raises:
2221+
HTTPError: If the API call fails
2222+
ValueError: If the label is invalid
2223+
"""
2224+
if not label:
2225+
raise ValueError("Label cannot be empty")
2226+
2227+
endpoint = self.get_endpoint("custom_content_labels", id=custom_content_id)
2228+
params = {"name": label, "prefix": prefix}
2229+
2230+
try:
2231+
self.delete(endpoint, params=params)
2232+
return True
2233+
except Exception as e:
2234+
log.error(f"Failed to delete label '{label}' from custom content {custom_content_id}: {e}")
2235+
raise
2236+
2237+
def get_custom_content_labels(
2238+
self, custom_content_id: str, prefix: Optional[str] = None, cursor: Optional[str] = None,
2239+
sort: Optional[str] = None, limit: int = 25
2240+
) -> List[Dict[str, Any]]:
2241+
"""
2242+
Returns all labels for custom content.
2243+
2244+
Args:
2245+
custom_content_id: The ID of the custom content
2246+
prefix: (optional) Filter the results to labels with a specific prefix
2247+
cursor: (optional) Cursor for pagination
2248+
sort: (optional) Sort order for the results. Valid values: 'name', '-name'
2249+
limit: (optional) Maximum number of labels to return per request. Default: 25
2250+
2251+
Returns:
2252+
List of label objects
2253+
2254+
Raises:
2255+
HTTPError: If the API call fails
2256+
"""
2257+
endpoint = self.get_endpoint("custom_content_labels", id=custom_content_id)
2258+
params = {"limit": limit}
2259+
2260+
if prefix:
2261+
params["prefix"] = prefix
21612262

21622263
if cursor:
21632264
params["cursor"] = cursor
21642265

2266+
if sort:
2267+
if sort not in ("name", "-name"):
2268+
raise ValueError("Sort must be one of 'name', '-name'")
2269+
params["sort"] = sort
2270+
21652271
try:
21662272
return list(self._get_paged(endpoint, params=params))
21672273
except Exception as e:
2168-
log.error(f"Failed to retrieve content property settings: {e}")
2274+
log.error(f"Failed to retrieve labels for custom content {custom_content_id}: {e}")
2275+
raise
2276+
2277+
def create_custom_content_property(self, custom_content_id: str, key: str, value: Any) -> Dict[str, Any]:
2278+
"""
2279+
Creates a new property for custom content.
2280+
2281+
Args:
2282+
custom_content_id: The ID of the custom content
2283+
key: The key of the property to create. Must only contain alphanumeric
2284+
characters, periods, and hyphens
2285+
value: The value of the property. Can be any JSON-serializable value
2286+
2287+
Returns:
2288+
The created property object
2289+
2290+
Raises:
2291+
HTTPError: If the API call fails
2292+
ValueError: If the key has invalid characters
2293+
"""
2294+
# Validate key format
2295+
if not re.match(r"^[a-zA-Z0-9.\-]+$", key):
2296+
raise ValueError("Property key must only contain alphanumeric characters, periods, and hyphens.")
2297+
2298+
endpoint = self.get_endpoint("custom_content_properties", id=custom_content_id)
2299+
2300+
data = {"key": key, "value": value}
2301+
2302+
try:
2303+
return self.post(endpoint, data=data)
2304+
except Exception as e:
2305+
log.error(f"Failed to create property {key} for custom content {custom_content_id}: {e}")
2306+
raise
2307+
2308+
def update_custom_content_property(
2309+
self, custom_content_id: str, key: str, value: Any, version_number: int, version_message: str = ""
2310+
) -> Dict[str, Any]:
2311+
"""
2312+
Updates an existing property for custom content.
2313+
2314+
Args:
2315+
custom_content_id: The ID of the custom content
2316+
key: The key of the property to update
2317+
value: The new value of the property. Can be any JSON-serializable value
2318+
version_number: The version number for concurrency control
2319+
version_message: (optional) A message describing the change
2320+
2321+
Returns:
2322+
The updated property object
2323+
2324+
Raises:
2325+
HTTPError: If the API call fails
2326+
"""
2327+
endpoint = self.get_endpoint("custom_content_property_by_key", id=custom_content_id, key=key)
2328+
2329+
data = {
2330+
"key": key,
2331+
"value": value,
2332+
"version": {"number": version_number, "message": version_message},
2333+
}
2334+
2335+
try:
2336+
return self.put(endpoint, data=data)
2337+
except Exception as e:
2338+
log.error(f"Failed to update property {key} for custom content {custom_content_id}: {e}")
2339+
raise
2340+
2341+
def delete_custom_content_property(self, custom_content_id: str, key: str) -> bool:
2342+
"""
2343+
Deletes a property from custom content.
2344+
2345+
Args:
2346+
custom_content_id: The ID of the custom content
2347+
key: The key of the property to delete
2348+
2349+
Returns:
2350+
True if the property was successfully deleted, False otherwise
2351+
2352+
Raises:
2353+
HTTPError: If the API call fails
2354+
"""
2355+
endpoint = self.get_endpoint("custom_content_property_by_key", id=custom_content_id, key=key)
2356+
2357+
try:
2358+
self.delete(endpoint)
2359+
return True
2360+
except Exception as e:
2361+
log.error(f"Failed to delete property {key} for custom content {custom_content_id}: {e}")
21692362
raise

tests/test_confluence_v2.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,18 +1100,19 @@ def test_get_custom_content_labels(self, mock_get_paged):
11001100
custom_content_id = "123456"
11011101
prefix = "global"
11021102
sort = "name"
1103+
limit = 25
11031104

11041105
mock_get_paged.return_value = [
11051106
{"id": "label1", "name": "test", "prefix": "global"},
11061107
{"id": "label2", "name": "documentation"},
11071108
]
11081109

11091110
result = self.confluence_v2.get_custom_content_labels(
1110-
custom_content_id=custom_content_id, prefix=prefix, sort=sort
1111+
custom_content_id=custom_content_id, prefix=prefix, sort=sort, limit=limit
11111112
)
11121113

11131114
mock_get_paged.assert_called_with(
1114-
f"api/v2/custom-content/{custom_content_id}/labels", params={"prefix": prefix, "sort": sort}
1115+
f"api/v2/custom-content/{custom_content_id}/labels", params={"prefix": prefix, "sort": sort, "limit": limit}
11151116
)
11161117

11171118
self.assertEqual(len(result), 2)

0 commit comments

Comments
 (0)