1+ """
2+ Confluence base module for shared functionality between API versions
3+ """
4+ import logging
5+ from typing import Dict , List , Optional , Union , Any , Tuple
6+
7+ from atlassian .rest_client import AtlassianRestAPI
8+
9+ log = logging .getLogger (__name__ )
10+
11+
12+ class ConfluenceEndpoints :
13+ """Class for storing Confluence endpoints for different API versions"""
14+
15+ V1 = {
16+ "page" : "rest/api/content" ,
17+ "page_by_id" : "rest/api/content/{id}" ,
18+ "child_pages" : "rest/api/content/{id}/child/page" ,
19+ "content_search" : "rest/api/content/search" ,
20+ "space" : "rest/api/space" ,
21+ "space_by_key" : "rest/api/space/{key}" ,
22+ }
23+
24+ V2 = {
25+ "page" : "api/v2/pages" ,
26+ "page_by_id" : "api/v2/pages/{id}" ,
27+ "child_pages" : "api/v2/pages/{id}/children" ,
28+ "content_search" : "api/v2/search" ,
29+ "space" : "api/v2/spaces" ,
30+ "space_by_key" : "api/v2/spaces/{key}" ,
31+ }
32+
33+
34+ class ConfluenceBase (AtlassianRestAPI ):
35+ """Base class for Confluence operations with version support"""
36+
37+ def __init__ (
38+ self ,
39+ url : str ,
40+ * args ,
41+ api_version : Union [str , int ] = 1 ,
42+ ** kwargs
43+ ):
44+ """
45+ Initialize the Confluence Base instance with version support.
46+
47+ Args:
48+ url: The Confluence instance URL
49+ api_version: API version, 1 or 2, defaults to 1
50+ args: Arguments to pass to AtlassianRestAPI constructor
51+ kwargs: Keyword arguments to pass to AtlassianRestAPI constructor
52+ """
53+ if ("atlassian.net" in url or "jira.com" in url ) and ("/wiki" not in url ):
54+ url = AtlassianRestAPI .url_joiner (url , "/wiki" )
55+ if "cloud" not in kwargs :
56+ kwargs ["cloud" ] = True
57+
58+ super (ConfluenceBase , self ).__init__ (url , * args , ** kwargs )
59+ self .api_version = int (api_version )
60+ if self .api_version not in [1 , 2 ]:
61+ raise ValueError ("API version must be 1 or 2" )
62+
63+ def get_endpoint (self , endpoint_key : str , ** kwargs ) -> str :
64+ """
65+ Get the appropriate endpoint based on the API version.
66+
67+ Args:
68+ endpoint_key: The key for the endpoint in the endpoints dictionary
69+ kwargs: Format parameters for the endpoint
70+
71+ Returns:
72+ The formatted endpoint URL
73+ """
74+ endpoints = ConfluenceEndpoints .V1 if self .api_version == 1 else ConfluenceEndpoints .V2
75+
76+ if endpoint_key not in endpoints :
77+ raise ValueError (f"Endpoint key '{ endpoint_key } ' not found for API version { self .api_version } " )
78+
79+ endpoint = endpoints [endpoint_key ]
80+
81+ # Format the endpoint if kwargs are provided
82+ if kwargs :
83+ endpoint = endpoint .format (** kwargs )
84+
85+ return endpoint
86+
87+ def _get_paged (
88+ self ,
89+ url : str ,
90+ params : Optional [Dict ] = None ,
91+ data : Optional [Dict ] = None ,
92+ flags : Optional [List ] = None ,
93+ trailing : Optional [bool ] = None ,
94+ absolute : bool = False ,
95+ ):
96+ """
97+ Get paged results with version-appropriate pagination.
98+
99+ Args:
100+ url: The URL to retrieve
101+ params: The query parameters
102+ data: The request data
103+ flags: Additional flags
104+ trailing: If True, a trailing slash is added to the URL
105+ absolute: If True, the URL is used absolute and not relative to the root
106+
107+ Yields:
108+ The result elements
109+ """
110+ if params is None :
111+ params = {}
112+
113+ if self .api_version == 1 :
114+ # V1 API pagination (offset-based)
115+ while True :
116+ response = self .get (
117+ url ,
118+ trailing = trailing ,
119+ params = params ,
120+ data = data ,
121+ flags = flags ,
122+ absolute = absolute ,
123+ )
124+ if "results" not in response :
125+ return
126+
127+ for value in response .get ("results" , []):
128+ yield value
129+
130+ # According to Cloud and Server documentation the links are returned the same way:
131+ # https://developer.atlassian.com/cloud/confluence/rest/api-group-content/#api-wiki-rest-api-content-get
132+ # https://developer.atlassian.com/server/confluence/pagination-in-the-rest-api/
133+ url = response .get ("_links" , {}).get ("next" )
134+ if url is None :
135+ break
136+ # From now on we have relative URLs with parameters
137+ absolute = False
138+ # Params are now provided by the url
139+ params = {}
140+ # Trailing should not be added as it is already part of the url
141+ trailing = False
142+
143+ else :
144+ # V2 API pagination (cursor-based)
145+ while True :
146+ response = self .get (
147+ url ,
148+ trailing = trailing ,
149+ params = params ,
150+ data = data ,
151+ flags = flags ,
152+ absolute = absolute ,
153+ )
154+
155+ if "results" not in response :
156+ return
157+
158+ for value in response .get ("results" , []):
159+ yield value
160+
161+ # Check for next cursor in _links or in response headers
162+ next_url = response .get ("_links" , {}).get ("next" )
163+
164+ if not next_url :
165+ # Check for Link header
166+ if hasattr (self , "response" ) and self .response and "Link" in self .response .headers :
167+ link_header = self .response .headers ["Link" ]
168+ if 'rel="next"' in link_header :
169+ import re
170+ match = re .search (r'<([^>]*)>;' , link_header )
171+ if match :
172+ next_url = match .group (1 )
173+
174+ if not next_url :
175+ break
176+
177+ # Use the next URL directly
178+ url = next_url
179+ absolute = False
180+ params = {}
181+ trailing = False
182+
183+ return
184+
185+ @staticmethod
186+ def factory (url : str , api_version : int = 1 , * args , ** kwargs ) -> 'ConfluenceBase' :
187+ """
188+ Factory method to create a Confluence client with the specified API version
189+
190+ Args:
191+ url: Confluence Cloud base URL
192+ api_version: API version to use (1 or 2)
193+ *args: Variable length argument list
194+ **kwargs: Keyword arguments
195+
196+ Returns:
197+ Configured Confluence client for the specified API version
198+
199+ Raises:
200+ ValueError: If api_version is not 1 or 2
201+ """
202+ if api_version == 1 :
203+ from .confluence import Confluence
204+ return Confluence (url , * args , ** kwargs )
205+ elif api_version == 2 :
206+ from .confluence_v2 import ConfluenceV2
207+ return ConfluenceV2 (url , * args , ** kwargs )
208+ else :
209+ raise ValueError (f"Unsupported API version: { api_version } . Use 1 or 2." )
0 commit comments