Implementation Guidelines¶
This document outlines the core design patterns and implementation guidelines for extending and maintaining the pyatlassian library. The library follows several key design patterns to ensure consistency, maintainability, and ease of use.
1. Source Code Organization Pattern¶
The pyatlassian library is organized using a hierarchical structure that reflects Atlassian’s unified API approach. Since Atlassian uses a single API endpoint for all its cloud services (Confluence, Jira, etc.), the codebase mirrors this architecture.
Python Library Folder Structure¶
pyatlassian/
├── atlassian/ # Base package with core functionality
│ ├── api.py # Base API implementation
│ └── model.py # Base Atlassian model
├── atlassian_confluence/ # Confluence-specific package
│ ├── api.py # Confluence API implementation
│ ├── model.py # Confluence model
│ └── utils.py # Confluence utilities
└── atlassian_jira/ # Jira-specific package (planned)
├── api.py # Jira API implementation
├── model.py # Jira model
└── utils.py # Jira utilities
...
Base Atlassian Implementation¶
The base Atlassian class serves as the foundation for all product-specific implementations:
atlassian/model.py
1# -*- coding: utf-8 -*-
2
3import typing as T
4import json
5import dataclasses
6from functools import cached_property
7
8import requests
9from requests.auth import HTTPBasicAuth
10
11from .exc import ParamError
12from .arg import REQ, _REQUIRED, NA, rm_na, T_KWARGS
13
14T_RESPONSE = T.Dict[str, T.Any]
15
16
17@dataclasses.dataclass
18class BaseModel:
19 def _validate(self):
20 for field in dataclasses.fields(self.__class__):
21 if field.init:
22 k = field.name
23 if getattr(self, k) is REQ: # pragma: no cover
24 raise ParamError(f"Field {k!r} is required for {self.__class__}.")
25
26 def __post_init__(self):
27 self._validate()
28
29 @classmethod
30 def _split_req_opt(cls, kwargs: T_KWARGS) -> T.Tuple[T_KWARGS, T_KWARGS]:
31 req_kwargs, opt_kwargs = dict(), dict()
32 for field in dataclasses.fields(cls):
33 if isinstance(field.default, _REQUIRED):
34 try:
35 req_kwargs[field.name] = kwargs[field.name]
36 except KeyError:
37 raise ParamError(
38 f"{field.name!r} is a required parameter for {cls}!"
39 )
40 else:
41 try:
42 opt_kwargs[field.name] = kwargs[field.name]
43 except KeyError:
44 pass
45 opt_kwargs = rm_na(**opt_kwargs)
46 return req_kwargs, opt_kwargs
47
48
49def _get_site_url(url: str) -> str:
50 """
51 Convert any of these url to https://mycompany.atlassian.net
52
53 - https://mycompany.atlassian.net/wiki/spaces/SPACEKEY/...
54 - https://mycompany.atlassian.net/jira/core/projects/PROJECTKEY/board/...
55 """
56 parts = url.split("/")
57 return "/".join(parts[:3])
58
59
60@dataclasses.dataclass
61class Atlassian(BaseModel):
62 url: str = dataclasses.field(default=REQ)
63 username: str = dataclasses.field(default=REQ)
64 password: str = dataclasses.field(default=NA)
65
66 def __post_init__(self):
67 self.url = _get_site_url(self.url)
68
69 @property
70 def headers(self) -> T.Dict[str, T.Any]:
71 return {"Content-Type": "application/json"}
72
73 @cached_property
74 def http_basic_auth(self) -> "HTTPBasicAuth":
75 return HTTPBasicAuth(username=self.username, password=self.password)
76
77 def request(
78 self,
79 method: str, # GET, POST, PUT, DELETE
80 url: str,
81 params: T.Optional[T_KWARGS] = None,
82 req_kwargs: T.Optional[T_KWARGS] = None,
83 ) -> "requests.Response":
84 """
85 Make HTTP request and get response.
86
87 :param req_kwargs: additional ``requests.request()`` kwargs
88 """
89 kwargs = dict(
90 method=method,
91 url=url,
92 headers=self.headers,
93 params=params,
94 auth=self.http_basic_auth,
95 )
96 if req_kwargs is not None:
97 kwargs.update(req_kwargs)
98 return requests.request(**kwargs)
99
100 def make_request(
101 self,
102 method: str,
103 url: str,
104 params: T.Optional[T.Dict[str, T.Any]] = None,
105 req_kwargs: T.Optional[T_KWARGS] = None,
106 ) -> T_RESPONSE:
107 """
108 Wrap the response object with better error handling.
109
110 :param req_kwargs: additional ``requests.request()`` kwargs
111 """
112 # print(f"{method = }") # for debug only
113 # print(f"{url = }") # for debug only
114 # print(f"{params = }") # for debug only
115 res = self.request(
116 method=method,
117 url=url,
118 params=params,
119 req_kwargs=req_kwargs,
120 )
121 res.raise_for_status()
122 return json.loads(res.text)
Product-Specific Implementation¶
Each Atlassian product (like Confluence) extends the base class:
atlassian_confluence/model.py
1# -*- coding: utf-8 -*-
2
3"""
4"""
5
6import typing as T
7import dataclasses
8from functools import cached_property
9
10from ..atlassian.api import (
11 Atlassian,
12 NA,
13 rm_na,
14 T_RESPONSE,
15 T_KWARGS,
16)
17
18from .children import ChildrenMixin
19from .label import LabelMixin
20from .page import PageMixin
21from .space import SpaceMixin
22
23
24@dataclasses.dataclass
25class Confluence(
26 Atlassian,
27 ChildrenMixin,
28 LabelMixin,
29 PageMixin,
30 SpaceMixin,
31):
32 """
33 - https://developer.atlassian.com/cloud/confluence/rest/v2/intro/#about
34 """
35
36 @cached_property
37 def _root_url(self) -> str:
38 return f"{self.url}/wiki/api/v2"
Utility Functions¶
Product-specific utilities are organized in dedicated modules:
atlassian_confluence/utils.py
1# -*- coding: utf-8 -*-
2
3import typing as T
4
5
6def extract_page_id_and_space_key_from_url(
7 url: str,
8) -> tuple[int, str]:
9 """
10 Extract page id (123456) and space key (ABC) from the URL like this
11
12 - https://mycompany.atlassian.net/wiki/spaces/ABC/pages/edit-v2/123456
13 - https://mycompany.atlassian.net/wiki/spaces/ABC/pages/123456/This+Document+Is+Awesome
14 """
15 if url.startswith("https://") or url.startswith("http://"):
16 url = "/".join(url.split("/")[2:])
17 parts = url.split("/")
18 if len(parts) >= 7:
19 if parts[2] == "spaces" and parts[4] == "pages":
20 if parts[5] == "edit-v2":
21 return int(parts[6]), parts[3]
22 else:
23 return int(parts[5]), parts[3]
24 else:
25 raise NotImplementedError
26 else:
27 raise NotImplementedError
I’ll help expand the API Group Modularization Pattern section to clearly explain these relationships. Here’s a comprehensive write-up:
2. API Group Modularization Pattern¶
The library uses a mixin-based modularization pattern to organize API endpoints into logical groups that mirror Atlassian’s official API documentation structure.
API Group to Module Mapping¶
Each API group in Atlassian’s REST API (e.g., pages, spaces, comments) corresponds to a dedicated Python module containing a mixin class:
Atlassian API Structure Python Implementation
──────────────────────── ───────────────────────
└── Confluence └── pyatlassian/atlassian_confluence/
├── Pages API Group ├── page.py (PageMixin)
└── Spaces API Group └── space.py (SpaceMixin)
Mixin Implementation Pattern¶
Each API group module implements a mixin class that encapsulates all endpoints within that group:
atlassian_confluence/page.py
1# -*- coding: utf-8 -*-
2
3"""
4"""
5
6import typing as T
7import dataclasses
8
9from ..pagi import _paginate
10from ..atlassian.api import (
11 NA,
12 rm_na,
13 T_RESPONSE,
14 T_KWARGS,
15)
16from .typehint import T_BODY_FORMAT, T_PAGE_STATUS, T_PAGE_SORT_ORDER
17
18if T.TYPE_CHECKING: # pragma: no cover
19 from .model import Confluence
20
21
22@dataclasses.dataclass
23class PageMixin:
24 """
25 For detailed API document, see:
26 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-group-page
27 """
28
29 def get_pages_for_label(
30 self: "Confluence",
31 id: int,
32 space_id: list[int] = NA,
33 body_format: T_BODY_FORMAT = NA,
34 sort: T_PAGE_SORT_ORDER = NA,
35 cursor: str = NA,
36 limit: int = NA,
37 req_kwargs: T.Optional[T_KWARGS] = None,
38 _url: str = None,
39 ) -> T_RESPONSE:
40 """
41 For detailed parameter descriptions, see:
42 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-labels-id-pages-get
43
44 :param req_kwargs: additional ``requests.request()`` kwargs
45 """
46 if _url is None:
47 url = f"{self._root_url}/labels/{id}/pages"
48 else:
49 url = _url
50 params = {
51 "space-id": space_id,
52 "body-format": body_format,
53 "sort": sort,
54 "cursor": cursor,
55 "limit": limit,
56 }
57 params = rm_na(**params)
58 params = params if len(params) else None
59 return self.make_request(
60 method="GET",
61 url=url,
62 params=params,
63 req_kwargs=req_kwargs,
64 )
65
66 def pagi_get_pages_for_label(
67 self: "Confluence",
68 id: int,
69 space_id: list[int] = NA,
70 body_format: T_BODY_FORMAT = NA,
71 sort: T_PAGE_SORT_ORDER = NA,
72 cursor: str = NA,
73 limit: int = NA,
74 req_kwargs: T.Optional[T_KWARGS] = None,
75 total_max_results: int = 9999,
76 ) -> T.Iterable[T_RESPONSE]:
77 """
78 For detailed parameter descriptions, see:
79 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-labels-id-pages-get
80
81 :param req_kwargs: additional ``requests.request()`` kwargs
82 :param total_max_results: total max results to fetch in all response
83 """
84
85 def get_next_token(res):
86 return res.get("_links", {}).get("next")
87
88 def set_next_token(kwargs, next_token):
89 kwargs["_url"] = f"{self.url}{next_token}"
90
91 yield from _paginate(
92 method=self.get_pages_for_label,
93 list_key="results",
94 get_next_token=get_next_token,
95 set_next_token=set_next_token,
96 kwargs=dict(
97 id=id,
98 space_id=space_id,
99 body_format=body_format,
100 sort=sort,
101 cursor=cursor,
102 limit=limit,
103 req_kwargs=req_kwargs,
104 ),
105 max_results=total_max_results,
106 )
107
108 def get_pages(
109 self: "Confluence",
110 id: list[int] = NA,
111 space_id: list[int] = NA,
112 sort: T_PAGE_SORT_ORDER = NA,
113 status: list[T_PAGE_STATUS] = NA,
114 title: str = NA,
115 body_format: T_BODY_FORMAT = NA,
116 cursor: str = NA,
117 limit: int = NA,
118 req_kwargs: T.Optional[T_KWARGS] = None,
119 _url: str = None,
120 ) -> T_RESPONSE:
121 """
122 For detailed parameter descriptions, see:
123 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-get
124
125 :param paginate: If True, automatically handle pagination
126 :param max_results: Maximum number of total results to return
127 when ``paginate = True``
128 :param req_kwargs: additional ``requests.request()`` kwargs
129 """
130 if _url is None:
131 url = f"{self._root_url}/pages"
132 else:
133 url = _url
134 params = {
135 "id": id,
136 "space-id": space_id,
137 "sort": sort,
138 "status": status,
139 "title": title,
140 "body-format": body_format,
141 "cursor": cursor,
142 "limit": limit,
143 }
144 params = rm_na(**params)
145 params = params if len(params) else None
146 return self.make_request(
147 method="GET",
148 url=url,
149 params=params,
150 req_kwargs=req_kwargs,
151 )
152
153 def pagi_get_pages(
154 self: "Confluence",
155 id: list[int] = NA,
156 space_id: list[int] = NA,
157 sort: T_PAGE_SORT_ORDER = NA,
158 status: list[T_PAGE_STATUS] = NA,
159 title: str = NA,
160 body_format: T_BODY_FORMAT = NA,
161 cursor: str = NA,
162 limit: int = NA,
163 req_kwargs: T.Optional[T_KWARGS] = None,
164 total_max_results: int = 9999,
165 ) -> T.Iterable[T_RESPONSE]:
166 """
167 For detailed parameter descriptions, see:
168 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-get
169
170 :param req_kwargs: additional ``requests.request()`` kwargs
171 :param total_max_results: total max results to fetch in all response
172 """
173
174 def get_next_token(res):
175 return res.get("_links", {}).get("next")
176
177 def set_next_token(kwargs, next_token):
178 kwargs["_url"] = f"{self.url}{next_token}"
179
180 yield from _paginate(
181 method=self.get_pages,
182 list_key="results",
183 get_next_token=get_next_token,
184 set_next_token=set_next_token,
185 kwargs=dict(
186 id=id,
187 space_id=space_id,
188 sort=sort,
189 status=status,
190 title=title,
191 body_format=body_format,
192 cursor=cursor,
193 limit=limit,
194 req_kwargs=req_kwargs,
195 ),
196 max_results=total_max_results,
197 )
198
199 def get_page_by_id(
200 self: "Confluence",
201 id: int,
202 body_format: T_BODY_FORMAT = NA,
203 get_draft: bool = NA,
204 status: list[
205 T.Literal[
206 "current",
207 "archived",
208 "trashed",
209 "deleted",
210 "historical",
211 "draft",
212 ]
213 ] = NA,
214 version: int = NA,
215 include_labels: bool = NA,
216 include_properties: bool = NA,
217 include_operations: bool = NA,
218 include_likes: bool = NA,
219 include_versions: bool = NA,
220 include_version: bool = NA,
221 include_favorited_by_current_user_status: bool = NA,
222 req_kwargs: T.Optional[T_KWARGS] = None,
223 ):
224 """
225 For detailed parameter descriptions, see:
226 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-id-get
227
228 :param req_kwargs: additional ``requests.request()`` kwargs
229 """
230 params = {
231 "body-format": body_format,
232 "get-draft": get_draft,
233 "status": status,
234 "version": version,
235 "include-labels": include_labels,
236 "include-properties": include_properties,
237 "include-operations": include_operations,
238 "include-likes": include_likes,
239 "include-versions": include_versions,
240 "include-version": include_version,
241 "include-favorited-by-current-user-status": include_favorited_by_current_user_status,
242 }
243 params = rm_na(**params)
244 params = params if len(params) else None
245 return self.make_request(
246 method="GET",
247 url=f"{self._root_url}/pages/{id}",
248 params=params,
249 req_kwargs=req_kwargs,
250 )
251
252 def get_pages_in_space(
253 self: "Confluence",
254 id: int,
255 depth: str = NA,
256 sort: str = NA,
257 status: list[
258 T.Literal[
259 "current",
260 "archived",
261 "trashed",
262 "deleted",
263 ]
264 ] = NA,
265 title: str = NA,
266 body_format: T_BODY_FORMAT = NA,
267 cursor: str = NA,
268 limit: int = NA,
269 req_kwargs: T.Optional[T_KWARGS] = None,
270 _url: str = None,
271 ) -> T_RESPONSE:
272 """
273 For detailed parameter descriptions, see:
274 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-spaces-id-pages-get
275
276 :param req_kwargs: additional ``requests.request()`` kwargs
277 """
278 if _url is None:
279 url = f"{self._root_url}/spaces/{id}/pages"
280 else:
281 url = _url
282 params = {
283 "depth": depth,
284 "sort": sort,
285 "status": status,
286 "title": title,
287 "body-format": body_format,
288 "cursor": cursor,
289 "limit": limit,
290 }
291 params = rm_na(**params)
292 params = params if len(params) else None
293 return self.make_request(
294 method="GET",
295 url=url,
296 params=params,
297 req_kwargs=req_kwargs,
298 )
299
300 def pagi_get_pages_in_space(
301 self: "Confluence",
302 id: int,
303 depth: str = NA,
304 sort: str = NA,
305 status: list[
306 T.Literal[
307 "current",
308 "archived",
309 "trashed",
310 "deleted",
311 ]
312 ] = NA,
313 title: str = NA,
314 body_format: T_BODY_FORMAT = NA,
315 cursor: str = NA,
316 limit: int = NA,
317 req_kwargs: T.Optional[T_KWARGS] = None,
318 total_max_results: int = 9999,
319 ) -> T.Iterable[T_RESPONSE]:
320 """
321 For detailed parameter descriptions, see:
322 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-spaces-id-pages-get
323
324 :param req_kwargs: additional ``requests.request()`` kwargs
325 :param total_max_results: total max results to fetch in all response
326 """
327
328 def get_next_token(res):
329 return res.get("_links", {}).get("next")
330
331 def set_next_token(kwargs, next_token):
332 kwargs["_url"] = f"{self.url}{next_token}"
333
334 yield from _paginate(
335 method=self.get_pages_in_space,
336 list_key="results",
337 get_next_token=get_next_token,
338 set_next_token=set_next_token,
339 kwargs=dict(
340 id=id,
341 depth=depth,
342 sort=sort,
343 status=status,
344 title=title,
345 body_format=body_format,
346 cursor=cursor,
347 limit=limit,
348 req_kwargs=req_kwargs,
349 ),
350 max_results=total_max_results,
351 )
API Method to Python Method Mapping¶
Each REST API endpoint maps to a method within its corresponding mixin class:
REST API Endpoint Python Method
────────────────── ─────────────
GET /spaces/{id}/pages → get_pages_in_space()
GET /pages/{id} → get_page_by_id()
POST /pages → create_page()
The Complete Model¶
The main model class (e.g., Confluence) inherits from all relevant mixins to provide a unified API:
atlassian_confluence/model.py
1# -*- coding: utf-8 -*-
2
3"""
4"""
5
6import typing as T
7import dataclasses
8from functools import cached_property
9
10from ..atlassian.api import (
11 Atlassian,
12 NA,
13 rm_na,
14 T_RESPONSE,
15 T_KWARGS,
16)
17
18from .children import ChildrenMixin
19from .label import LabelMixin
20from .page import PageMixin
21from .space import SpaceMixin
22
23
24@dataclasses.dataclass
25class Confluence(
26 Atlassian,
27 ChildrenMixin,
28 LabelMixin,
29 PageMixin,
30 SpaceMixin,
31):
32 """
33 - https://developer.atlassian.com/cloud/confluence/rest/v2/intro/#about
34 """
35
36 @cached_property
37 def _root_url(self) -> str:
38 return f"{self.url}/wiki/api/v2"
This pattern offers several benefits:
Modularity: Each API group is self-contained and independently maintainable
Organization: Clear mapping between Atlassian’s API documentation and code structure
Extensibility: Easy to add new API groups or methods
Reusability: Mixins can be combined flexibly for different use cases
Maintainability: Changes to one API group don’t affect others
3. Method Naming Convention Pattern¶
The library follows a consistent method naming convention that converts Atlassian’s REST API endpoints into Pythonic method names while preserving semantics and readability.
Method Name Transformation Rules¶
Methods are named by slugifying the method names found in the official Atlassian documentation, not the API paths. This ensures semantic clarity and alignment with official documentation.
# Official Doc: "Get pages in space"
→ get_pages_in_space()
# Official Doc: "Get page by ID"
→ get_page_by_id()
Implementation Examples¶
atlassian_confluence/page.py
1# -*- coding: utf-8 -*-
2
3"""
4"""
5
6import typing as T
7import dataclasses
8
9from ..pagi import _paginate
10from ..atlassian.api import (
11 NA,
12 rm_na,
13 T_RESPONSE,
14 T_KWARGS,
15)
16from .typehint import T_BODY_FORMAT, T_PAGE_STATUS, T_PAGE_SORT_ORDER
17
18if T.TYPE_CHECKING: # pragma: no cover
19 from .model import Confluence
20
21
22@dataclasses.dataclass
23class PageMixin:
24 """
25 For detailed API document, see:
26 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-group-page
27 """
28
29 def get_pages_for_label(
30 self: "Confluence",
31 id: int,
32 space_id: list[int] = NA,
33 body_format: T_BODY_FORMAT = NA,
34 sort: T_PAGE_SORT_ORDER = NA,
35 cursor: str = NA,
36 limit: int = NA,
37 req_kwargs: T.Optional[T_KWARGS] = None,
38 _url: str = None,
39 ) -> T_RESPONSE:
40 """
41 For detailed parameter descriptions, see:
42 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-labels-id-pages-get
43
44 :param req_kwargs: additional ``requests.request()`` kwargs
45 """
46 if _url is None:
47 url = f"{self._root_url}/labels/{id}/pages"
48 else:
49 url = _url
50 params = {
51 "space-id": space_id,
52 "body-format": body_format,
53 "sort": sort,
54 "cursor": cursor,
55 "limit": limit,
56 }
57 params = rm_na(**params)
58 params = params if len(params) else None
59 return self.make_request(
60 method="GET",
61 url=url,
62 params=params,
63 req_kwargs=req_kwargs,
64 )
65
66 def pagi_get_pages_for_label(
67 self: "Confluence",
68 id: int,
69 space_id: list[int] = NA,
70 body_format: T_BODY_FORMAT = NA,
71 sort: T_PAGE_SORT_ORDER = NA,
72 cursor: str = NA,
73 limit: int = NA,
74 req_kwargs: T.Optional[T_KWARGS] = None,
75 total_max_results: int = 9999,
76 ) -> T.Iterable[T_RESPONSE]:
77 """
78 For detailed parameter descriptions, see:
79 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-labels-id-pages-get
80
81 :param req_kwargs: additional ``requests.request()`` kwargs
82 :param total_max_results: total max results to fetch in all response
83 """
84
85 def get_next_token(res):
86 return res.get("_links", {}).get("next")
87
88 def set_next_token(kwargs, next_token):
89 kwargs["_url"] = f"{self.url}{next_token}"
90
91 yield from _paginate(
92 method=self.get_pages_for_label,
93 list_key="results",
94 get_next_token=get_next_token,
95 set_next_token=set_next_token,
96 kwargs=dict(
97 id=id,
98 space_id=space_id,
99 body_format=body_format,
100 sort=sort,
101 cursor=cursor,
102 limit=limit,
103 req_kwargs=req_kwargs,
104 ),
105 max_results=total_max_results,
106 )
107
108 def get_pages(
109 self: "Confluence",
110 id: list[int] = NA,
111 space_id: list[int] = NA,
112 sort: T_PAGE_SORT_ORDER = NA,
113 status: list[T_PAGE_STATUS] = NA,
114 title: str = NA,
115 body_format: T_BODY_FORMAT = NA,
116 cursor: str = NA,
117 limit: int = NA,
118 req_kwargs: T.Optional[T_KWARGS] = None,
119 _url: str = None,
120 ) -> T_RESPONSE:
121 """
122 For detailed parameter descriptions, see:
123 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-get
124
125 :param paginate: If True, automatically handle pagination
126 :param max_results: Maximum number of total results to return
127 when ``paginate = True``
128 :param req_kwargs: additional ``requests.request()`` kwargs
129 """
130 if _url is None:
131 url = f"{self._root_url}/pages"
132 else:
133 url = _url
134 params = {
135 "id": id,
136 "space-id": space_id,
137 "sort": sort,
138 "status": status,
139 "title": title,
140 "body-format": body_format,
141 "cursor": cursor,
142 "limit": limit,
143 }
144 params = rm_na(**params)
145 params = params if len(params) else None
146 return self.make_request(
147 method="GET",
148 url=url,
149 params=params,
150 req_kwargs=req_kwargs,
151 )
152
153 def pagi_get_pages(
154 self: "Confluence",
155 id: list[int] = NA,
156 space_id: list[int] = NA,
157 sort: T_PAGE_SORT_ORDER = NA,
158 status: list[T_PAGE_STATUS] = NA,
159 title: str = NA,
160 body_format: T_BODY_FORMAT = NA,
161 cursor: str = NA,
162 limit: int = NA,
163 req_kwargs: T.Optional[T_KWARGS] = None,
164 total_max_results: int = 9999,
165 ) -> T.Iterable[T_RESPONSE]:
166 """
167 For detailed parameter descriptions, see:
168 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-get
169
170 :param req_kwargs: additional ``requests.request()`` kwargs
171 :param total_max_results: total max results to fetch in all response
172 """
173
174 def get_next_token(res):
175 return res.get("_links", {}).get("next")
176
177 def set_next_token(kwargs, next_token):
178 kwargs["_url"] = f"{self.url}{next_token}"
179
180 yield from _paginate(
181 method=self.get_pages,
182 list_key="results",
183 get_next_token=get_next_token,
184 set_next_token=set_next_token,
185 kwargs=dict(
186 id=id,
187 space_id=space_id,
188 sort=sort,
189 status=status,
190 title=title,
191 body_format=body_format,
192 cursor=cursor,
193 limit=limit,
194 req_kwargs=req_kwargs,
195 ),
196 max_results=total_max_results,
197 )
198
199 def get_page_by_id(
200 self: "Confluence",
201 id: int,
202 body_format: T_BODY_FORMAT = NA,
203 get_draft: bool = NA,
204 status: list[
205 T.Literal[
206 "current",
207 "archived",
208 "trashed",
209 "deleted",
210 "historical",
211 "draft",
212 ]
213 ] = NA,
214 version: int = NA,
215 include_labels: bool = NA,
216 include_properties: bool = NA,
217 include_operations: bool = NA,
218 include_likes: bool = NA,
219 include_versions: bool = NA,
220 include_version: bool = NA,
221 include_favorited_by_current_user_status: bool = NA,
222 req_kwargs: T.Optional[T_KWARGS] = None,
223 ):
224 """
225 For detailed parameter descriptions, see:
226 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-id-get
227
228 :param req_kwargs: additional ``requests.request()`` kwargs
229 """
230 params = {
231 "body-format": body_format,
232 "get-draft": get_draft,
233 "status": status,
234 "version": version,
235 "include-labels": include_labels,
236 "include-properties": include_properties,
237 "include-operations": include_operations,
238 "include-likes": include_likes,
239 "include-versions": include_versions,
240 "include-version": include_version,
241 "include-favorited-by-current-user-status": include_favorited_by_current_user_status,
242 }
243 params = rm_na(**params)
244 params = params if len(params) else None
245 return self.make_request(
246 method="GET",
247 url=f"{self._root_url}/pages/{id}",
248 params=params,
249 req_kwargs=req_kwargs,
250 )
251
252 def get_pages_in_space(
253 self: "Confluence",
254 id: int,
255 depth: str = NA,
256 sort: str = NA,
257 status: list[
258 T.Literal[
259 "current",
260 "archived",
261 "trashed",
262 "deleted",
263 ]
264 ] = NA,
265 title: str = NA,
266 body_format: T_BODY_FORMAT = NA,
267 cursor: str = NA,
268 limit: int = NA,
269 req_kwargs: T.Optional[T_KWARGS] = None,
270 _url: str = None,
271 ) -> T_RESPONSE:
272 """
273 For detailed parameter descriptions, see:
274 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-spaces-id-pages-get
275
276 :param req_kwargs: additional ``requests.request()`` kwargs
277 """
278 if _url is None:
279 url = f"{self._root_url}/spaces/{id}/pages"
280 else:
281 url = _url
282 params = {
283 "depth": depth,
284 "sort": sort,
285 "status": status,
286 "title": title,
287 "body-format": body_format,
288 "cursor": cursor,
289 "limit": limit,
290 }
291 params = rm_na(**params)
292 params = params if len(params) else None
293 return self.make_request(
294 method="GET",
295 url=url,
296 params=params,
297 req_kwargs=req_kwargs,
298 )
299
300 def pagi_get_pages_in_space(
301 self: "Confluence",
302 id: int,
303 depth: str = NA,
304 sort: str = NA,
305 status: list[
306 T.Literal[
307 "current",
308 "archived",
309 "trashed",
310 "deleted",
311 ]
312 ] = NA,
313 title: str = NA,
314 body_format: T_BODY_FORMAT = NA,
315 cursor: str = NA,
316 limit: int = NA,
317 req_kwargs: T.Optional[T_KWARGS] = None,
318 total_max_results: int = 9999,
319 ) -> T.Iterable[T_RESPONSE]:
320 """
321 For detailed parameter descriptions, see:
322 https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-spaces-id-pages-get
323
324 :param req_kwargs: additional ``requests.request()`` kwargs
325 :param total_max_results: total max results to fetch in all response
326 """
327
328 def get_next_token(res):
329 return res.get("_links", {}).get("next")
330
331 def set_next_token(kwargs, next_token):
332 kwargs["_url"] = f"{self.url}{next_token}"
333
334 yield from _paginate(
335 method=self.get_pages_in_space,
336 list_key="results",
337 get_next_token=get_next_token,
338 set_next_token=set_next_token,
339 kwargs=dict(
340 id=id,
341 depth=depth,
342 sort=sort,
343 status=status,
344 title=title,
345 body_format=body_format,
346 cursor=cursor,
347 limit=limit,
348 req_kwargs=req_kwargs,
349 ),
350 max_results=total_max_results,
351 )
Parameter Naming Rules¶
Path Parameters
# API: /spaces/{id}/pages
def get_pages_in_space(
self,
id: int,
...
): # 'id' matches path parameter
pass
Query Parameters
# API: ?body-format=storage&get-draft=true def get_page_by_id( self, id: int, body_format: str = NA, # hyphen → underscore get_draft: bool = NA, # get-draft → get_draft ... ): ...
4. Parameter Handling Pattern¶
The library makes a clear distinction between required and optional parameters using Python’s type system and special singleton objects. Required parameters are enforced at initialization, while optional parameters can be omitted.
def get_pages_in_space(
self,
id: int, # Required: no default value
depth: str = NA, # Optional: defaults to NA
sort: str = NA, # Optional: defaults to NA
status: list = NA, # Optional: defaults to NA
...
):
params = {
"depth": depth,
"sort": sort,
"status": status,
}
# Remove NA parameters before making request
params = rm_na(params)
params = params if len(params) else None
res = self.make_request(
method="GET",
url=_url,
params=params,
)
Key Concepts:
- Required Parameters
Must be provided by the caller
No default value assigned
Enforced through Python’s argument system
Example: id
- Optional Parameters
Can be omitted by the caller
Default value is
NAsingletonAutomatically filtered out using
rm_naExample: limit, sort, status
5. Pagination Implementation Pattern¶
The library implements a consistent pagination pattern for API endpoints that return paginated results. This pattern handles both manual and automatic pagination while maintaining a clean interface.
The pagination implementation uses recursive calls with accumulation, controlled by specific pagination parameters:
- Control Parameters
paginate: bool = False- Enables/disables automatic paginationmax_results: int = 9999- Maximum number of results to return_url: str = None- Internal parameter for continuation URLs_results: list = None- Internal parameter for result accumulation
Base URL and Results Initia lization
if _url is None:
_url = f"{self._root_url}/spaces/{id}/pages"
if _results is None:
_results = []
if len(_results) >= max_results:
return {"results": _results}
Result Accumulation
_results.extend(res.get("results", []))
Pagination Control Flow
if "next" in res["_links"] and paginate:
_url = f"{self.url}{res['_links']['next']}"
_res = self.get_pages_in_space(
# ... pass through all original parameters ...
_url=_url,
_results=_results,
)
Key Benefits
Transparency: Users can choose between single-page and automatic pagination
Control:
max_resultsparameter prevents unbounded result setsEfficiency: Results are accumulated incrementally
Consistency: Same method signature works for both paginated and non-paginated requests
Clean Interface: Internal pagination parameters are hidden from API documentation
Implementation Notes
Internal parameters (
_url,_results) should not be exposed in public documentationResult accumulation preserves the original API response structure
Error handling and rate limiting should be considered in the implementation