chub-dev 0.1.0 → 0.1.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1604 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: confluence
|
|
3
|
+
description: "Confluence Cloud API coding guidelines for Python using the atlassian-python-api library"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "python"
|
|
6
|
+
versions: "4.0.7"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "atlassian,confluence,wiki,documentation,collaboration"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Confluence Cloud API Coding Guidelines (Python)
|
|
13
|
+
|
|
14
|
+
You are a **Confluence Cloud API coding expert**. Help me write correct, idiomatic Python code that calls the Atlassian Confluence Cloud REST API using the atlassian-python-api library.
|
|
15
|
+
|
|
16
|
+
Use **only official Atlassian sources** for API behavior, fields, and constraints. This guide summarizes key patterns for Python applications.
|
|
17
|
+
|
|
18
|
+
> Ground truth: Atlassian Confluence Cloud REST API documentation at developer.atlassian.com/cloud/confluence/
|
|
19
|
+
|
|
20
|
+
## Golden Rule: Use atlassian-python-api Library
|
|
21
|
+
|
|
22
|
+
**CRITICAL:** Use the `atlassian-python-api` package (version 4.0.7 or later) for interacting with Confluence Cloud REST API. This is the most comprehensive and actively maintained community library for Atlassian products.
|
|
23
|
+
|
|
24
|
+
**DO NOT use:**
|
|
25
|
+
- `pyatlassian` (different package, less maintained)
|
|
26
|
+
- `confluence-api` (JavaScript package, not Python)
|
|
27
|
+
- Direct REST API calls with `requests` without a client library (error-prone)
|
|
28
|
+
|
|
29
|
+
**Install:**
|
|
30
|
+
```bash
|
|
31
|
+
pip install atlassian-python-api
|
|
32
|
+
# or
|
|
33
|
+
poetry add atlassian-python-api
|
|
34
|
+
# or
|
|
35
|
+
uv pip install atlassian-python-api
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The library supports Python 3.8+ and provides comprehensive coverage of Confluence Cloud and Server APIs.
|
|
39
|
+
|
|
40
|
+
## Authentication
|
|
41
|
+
|
|
42
|
+
Confluence Cloud requires API token authentication. Username/password authentication is deprecated.
|
|
43
|
+
|
|
44
|
+
### API Token Authentication - Required for Cloud
|
|
45
|
+
|
|
46
|
+
Generate an API token from your Atlassian Account Settings (https://id.atlassian.com/manage-profile/security/api-tokens).
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from atlassian import Confluence
|
|
50
|
+
|
|
51
|
+
confluence = Confluence(
|
|
52
|
+
url='https://your-domain.atlassian.net',
|
|
53
|
+
username='your-email@example.com',
|
|
54
|
+
password='your-api-token', # This is your API token, not your password
|
|
55
|
+
cloud=True
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**IMPORTANT:** For Confluence Cloud, you must:
|
|
60
|
+
1. Use your email address as the username
|
|
61
|
+
2. Use an API token (not your account password) as the password
|
|
62
|
+
3. Set `cloud=True`
|
|
63
|
+
|
|
64
|
+
### Environment Variables Setup
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# .env file
|
|
68
|
+
CONFLUENCE_URL=https://your-domain.atlassian.net
|
|
69
|
+
CONFLUENCE_USERNAME=your-email@example.com
|
|
70
|
+
CONFLUENCE_API_TOKEN=your_api_token_here
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Loading in Python:**
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import os
|
|
77
|
+
from dotenv import load_dotenv
|
|
78
|
+
from atlassian import Confluence
|
|
79
|
+
|
|
80
|
+
load_dotenv()
|
|
81
|
+
|
|
82
|
+
confluence = Confluence(
|
|
83
|
+
url=os.getenv('CONFLUENCE_URL'),
|
|
84
|
+
username=os.getenv('CONFLUENCE_USERNAME'),
|
|
85
|
+
password=os.getenv('CONFLUENCE_API_TOKEN'),
|
|
86
|
+
cloud=True
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### OAuth 2.0 Authentication
|
|
91
|
+
|
|
92
|
+
For apps using OAuth 2.0 access tokens:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from atlassian import Confluence
|
|
96
|
+
|
|
97
|
+
confluence = Confluence(
|
|
98
|
+
url='https://your-domain.atlassian.net',
|
|
99
|
+
token='your-oauth-access-token',
|
|
100
|
+
cloud=True
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Server/Data Center Authentication (On-Premise)
|
|
105
|
+
|
|
106
|
+
For self-hosted Confluence instances:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from atlassian import Confluence
|
|
110
|
+
|
|
111
|
+
confluence = Confluence(
|
|
112
|
+
url='https://confluence.company.com',
|
|
113
|
+
username='your-username',
|
|
114
|
+
password='your-password',
|
|
115
|
+
cloud=False # Important for Server/Data Center
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Initialization Patterns
|
|
120
|
+
|
|
121
|
+
### Basic Initialization
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from atlassian import Confluence
|
|
125
|
+
|
|
126
|
+
confluence = Confluence(
|
|
127
|
+
url='https://your-domain.atlassian.net',
|
|
128
|
+
username='your-email@example.com',
|
|
129
|
+
password='your-api-token',
|
|
130
|
+
cloud=True
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### With Timeout Configuration
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
confluence = Confluence(
|
|
138
|
+
url='https://your-domain.atlassian.net',
|
|
139
|
+
username='your-email@example.com',
|
|
140
|
+
password='your-api-token',
|
|
141
|
+
cloud=True,
|
|
142
|
+
timeout=60 # Seconds
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### With Proxy Support
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
confluence = Confluence(
|
|
150
|
+
url='https://your-domain.atlassian.net',
|
|
151
|
+
username='your-email@example.com',
|
|
152
|
+
password='your-api-token',
|
|
153
|
+
cloud=True,
|
|
154
|
+
proxies={
|
|
155
|
+
'http': 'http://proxy.company.com:8080',
|
|
156
|
+
'https': 'https://proxy.company.com:8080',
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Verify SSL Certificates
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# Disable SSL verification (not recommended for production)
|
|
165
|
+
confluence = Confluence(
|
|
166
|
+
url='https://your-domain.atlassian.net',
|
|
167
|
+
username='your-email@example.com',
|
|
168
|
+
password='your-api-token',
|
|
169
|
+
cloud=True,
|
|
170
|
+
verify_ssl=False
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Pages API
|
|
175
|
+
|
|
176
|
+
### Get Page by ID
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
page = confluence.get_page_by_id(
|
|
180
|
+
page_id='123456789',
|
|
181
|
+
expand='body.storage,version,space'
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
print(f"Title: {page['title']}")
|
|
185
|
+
print(f"Space: {page['space']['key']}")
|
|
186
|
+
print(f"Version: {page['version']['number']}")
|
|
187
|
+
print(f"Content: {page['body']['storage']['value']}")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Get Page by Title
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
page = confluence.get_page_by_title(
|
|
194
|
+
space='DEMO',
|
|
195
|
+
title='Getting Started'
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if page:
|
|
199
|
+
print(f"Page ID: {page['id']}")
|
|
200
|
+
print(f"Title: {page['title']}")
|
|
201
|
+
else:
|
|
202
|
+
print("Page not found")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Get Page ID Only
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
page_id = confluence.get_page_id(
|
|
209
|
+
space='DEMO',
|
|
210
|
+
title='Getting Started'
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if page_id:
|
|
214
|
+
print(f"Found page: {page_id}")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Get All Pages from Space
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
pages = confluence.get_all_pages_from_space(
|
|
221
|
+
space='DEMO',
|
|
222
|
+
start=0,
|
|
223
|
+
limit=100,
|
|
224
|
+
status='current',
|
|
225
|
+
expand='version,space'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
for page in pages:
|
|
229
|
+
print(f"{page['title']} - {page['id']}")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Get All Pages as Generator (Memory Efficient)
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
for page in confluence.get_all_pages_from_space_as_generator(
|
|
236
|
+
space='DEMO',
|
|
237
|
+
expand='body.storage,version'
|
|
238
|
+
):
|
|
239
|
+
print(f"Processing: {page['title']}")
|
|
240
|
+
# Process each page without loading all into memory
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Create a Page
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
page = confluence.create_page(
|
|
247
|
+
space='DEMO',
|
|
248
|
+
title='Getting Started Guide',
|
|
249
|
+
body='<h1>Welcome</h1><p>This is the introduction to our project.</p>',
|
|
250
|
+
parent_id=None, # Optional: specify parent page ID
|
|
251
|
+
type='page',
|
|
252
|
+
representation='storage'
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
print(f"Created page ID: {page['id']}")
|
|
256
|
+
print(f"URL: {page['_links']['webui']}")
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Create a Child Page
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
parent_id = confluence.get_page_id(space='DEMO', title='Parent Page')
|
|
263
|
+
|
|
264
|
+
child_page = confluence.create_page(
|
|
265
|
+
space='DEMO',
|
|
266
|
+
title='Child Page',
|
|
267
|
+
body='<p>This is a child page.</p>',
|
|
268
|
+
parent_id=parent_id
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
print(f"Created child page: {child_page['id']}")
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Update a Page
|
|
275
|
+
|
|
276
|
+
**IMPORTANT:** Always increment the version number when updating.
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
# Get current page to retrieve version
|
|
280
|
+
page = confluence.get_page_by_id(
|
|
281
|
+
page_id='123456789',
|
|
282
|
+
expand='version,space'
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Update with incremented version
|
|
286
|
+
updated = confluence.update_page(
|
|
287
|
+
page_id='123456789',
|
|
288
|
+
title='Updated Title',
|
|
289
|
+
body='<h1>Updated Content</h1><p>New information here.</p>',
|
|
290
|
+
version_number=page['version']['number'] + 1,
|
|
291
|
+
representation='storage'
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
print(f"Updated to version {updated['version']['number']}")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Update Page Content Only (Keep Title)
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
page = confluence.get_page_by_id(
|
|
301
|
+
page_id='123456789',
|
|
302
|
+
expand='version,body.storage'
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
confluence.update_page(
|
|
306
|
+
page_id='123456789',
|
|
307
|
+
title=page['title'], # Keep existing title
|
|
308
|
+
body='<p>Only the content changed.</p>',
|
|
309
|
+
version_number=page['version']['number'] + 1
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Append to Page Content
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
page = confluence.get_page_by_id(
|
|
317
|
+
page_id='123456789',
|
|
318
|
+
expand='version,body.storage'
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
current_content = page['body']['storage']['value']
|
|
322
|
+
new_content = current_content + '<p>Additional content appended.</p>'
|
|
323
|
+
|
|
324
|
+
confluence.update_page(
|
|
325
|
+
page_id='123456789',
|
|
326
|
+
title=page['title'],
|
|
327
|
+
body=new_content,
|
|
328
|
+
version_number=page['version']['number'] + 1
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Delete a Page
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
confluence.remove_page(
|
|
336
|
+
page_id='123456789',
|
|
337
|
+
status=None # Use 'trashed' for soft delete
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
print("Page deleted successfully")
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Move Page to Trash (Soft Delete)
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
confluence.set_page_property(
|
|
347
|
+
page_id='123456789',
|
|
348
|
+
data={'status': 'trashed'}
|
|
349
|
+
)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Get Page Children
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
children = confluence.get_page_child_by_type(
|
|
356
|
+
page_id='123456789',
|
|
357
|
+
type='page',
|
|
358
|
+
start=0,
|
|
359
|
+
limit=50
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
for child in children:
|
|
363
|
+
print(f"Child: {child['title']} ({child['id']})")
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Get Page Descendants
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
descendants = confluence.get_page_descendants(
|
|
370
|
+
page_id='123456789',
|
|
371
|
+
type='page'
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
for page in descendants:
|
|
375
|
+
print(f"Descendant: {page['title']}")
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Get Page Ancestors
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
page = confluence.get_page_by_id(
|
|
382
|
+
page_id='123456789',
|
|
383
|
+
expand='ancestors'
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
for ancestor in page.get('ancestors', []):
|
|
387
|
+
print(f"Ancestor: {ancestor['title']} ({ancestor['id']})")
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Get Page History
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
history = confluence.history(page_id='123456789')
|
|
394
|
+
|
|
395
|
+
print(f"Created by: {history['createdBy']['displayName']}")
|
|
396
|
+
print(f"Created date: {history['createdDate']}")
|
|
397
|
+
print(f"Latest version: {history['latest']['number']}")
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Get Page Version Information
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
version = confluence.get_page_version(
|
|
404
|
+
page_id='123456789',
|
|
405
|
+
version_number=None # None for latest, or specify version number
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
print(f"Version: {version['number']}")
|
|
409
|
+
print(f"Modified by: {version['by']['displayName']}")
|
|
410
|
+
print(f"Modified when: {version['when']}")
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Spaces API
|
|
414
|
+
|
|
415
|
+
### Get Space
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
space = confluence.get_space(
|
|
419
|
+
space_key='DEMO',
|
|
420
|
+
expand='description.plain,homepage'
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
print(f"Name: {space['name']}")
|
|
424
|
+
print(f"Key: {space['key']}")
|
|
425
|
+
print(f"Type: {space['type']}")
|
|
426
|
+
if 'description' in space:
|
|
427
|
+
print(f"Description: {space['description']['plain']['value']}")
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Get All Spaces
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
spaces = confluence.get_all_spaces(
|
|
434
|
+
start=0,
|
|
435
|
+
limit=100,
|
|
436
|
+
expand='description.plain'
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
for space in spaces['results']:
|
|
440
|
+
print(f"{space['key']}: {space['name']}")
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Get All Spaces as Generator
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
for space in confluence.get_all_spaces_as_generator():
|
|
447
|
+
print(f"Space: {space['key']} - {space['name']}")
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Create a Space
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
space = confluence.create_space(
|
|
454
|
+
space_key='PROJ',
|
|
455
|
+
space_name='Project Galaxy',
|
|
456
|
+
description='Documentation for Project Galaxy'
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
print(f"Created space: {space['key']}")
|
|
460
|
+
print(f"URL: {space['_links']['webui']}")
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Update Space
|
|
464
|
+
|
|
465
|
+
```python
|
|
466
|
+
confluence.update_space(
|
|
467
|
+
space_key='DEMO',
|
|
468
|
+
name='Demo Space - Updated',
|
|
469
|
+
description='Updated description'
|
|
470
|
+
)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Delete Space
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
confluence.delete_space(space_key='OLDSPACE')
|
|
477
|
+
print("Space deleted successfully")
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Get Space Content
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
content = confluence.get_space_content(
|
|
484
|
+
space_key='DEMO',
|
|
485
|
+
depth='all',
|
|
486
|
+
start=0,
|
|
487
|
+
limit=100
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
for item in content.get('page', {}).get('results', []):
|
|
491
|
+
print(f"Page: {item['title']} ({item['id']})")
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Get Space Homepage
|
|
495
|
+
|
|
496
|
+
```python
|
|
497
|
+
space = confluence.get_space(
|
|
498
|
+
space_key='DEMO',
|
|
499
|
+
expand='homepage'
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
if 'homepage' in space:
|
|
503
|
+
homepage = space['homepage']
|
|
504
|
+
print(f"Homepage: {homepage['title']} ({homepage['id']})")
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## Search API
|
|
508
|
+
|
|
509
|
+
### CQL Search (Confluence Query Language)
|
|
510
|
+
|
|
511
|
+
```python
|
|
512
|
+
results = confluence.cql(
|
|
513
|
+
cql='type=page AND space=DEMO AND title~"getting started"',
|
|
514
|
+
start=0,
|
|
515
|
+
limit=20,
|
|
516
|
+
expand='content.space,content.version'
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
for result in results.get('results', []):
|
|
520
|
+
content = result.get('content', {})
|
|
521
|
+
print(f"{content.get('title')} - {content.get('id')}")
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Common CQL Query Patterns
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
# Find all pages in a space
|
|
528
|
+
results = confluence.cql('type=page AND space=DEMO')
|
|
529
|
+
|
|
530
|
+
# Find pages by creator
|
|
531
|
+
results = confluence.cql('type=page AND creator=currentUser()')
|
|
532
|
+
|
|
533
|
+
# Find recently modified content
|
|
534
|
+
results = confluence.cql(
|
|
535
|
+
'type=page AND lastModified > now("-7d") ORDER BY lastModified DESC',
|
|
536
|
+
limit=10
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
# Find pages with specific label
|
|
540
|
+
results = confluence.cql('type=page AND label="documentation"')
|
|
541
|
+
|
|
542
|
+
# Complex search with AND/OR
|
|
543
|
+
results = confluence.cql(
|
|
544
|
+
'type=page AND space IN (DEMO, PROJ) AND (title~"guide" OR text~"tutorial")'
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# Find pages modified by specific user
|
|
548
|
+
results = confluence.cql('type=page AND contributor="john.doe@example.com"')
|
|
549
|
+
|
|
550
|
+
# Find pages created this month
|
|
551
|
+
results = confluence.cql('type=page AND created >= startOfMonth()')
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Search Content by Title
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
# Using get_page_by_title for exact match
|
|
558
|
+
page = confluence.get_page_by_title(
|
|
559
|
+
space='DEMO',
|
|
560
|
+
title='Installation'
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
# Using CQL for partial match
|
|
564
|
+
results = confluence.cql('type=page AND title~"installation"')
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Full-Text Search
|
|
568
|
+
|
|
569
|
+
```python
|
|
570
|
+
results = confluence.cql(
|
|
571
|
+
'type=page AND text~"kubernetes deployment"',
|
|
572
|
+
limit=50
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
for result in results.get('results', []):
|
|
576
|
+
content = result.get('content', {})
|
|
577
|
+
print(f"Found: {content.get('title')}")
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Attachments API
|
|
581
|
+
|
|
582
|
+
### Get Attachments for Page
|
|
583
|
+
|
|
584
|
+
```python
|
|
585
|
+
attachments = confluence.get_attachments_from_content(
|
|
586
|
+
page_id='123456789',
|
|
587
|
+
start=0,
|
|
588
|
+
limit=50,
|
|
589
|
+
filename=None, # Optional: filter by filename
|
|
590
|
+
media_type=None # Optional: filter by media type
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
for attachment in attachments.get('results', []):
|
|
594
|
+
print(f"{attachment['title']} - {attachment['extensions']['fileSize']} bytes")
|
|
595
|
+
print(f"Download: {attachment['_links']['download']}")
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Attach File to Page
|
|
599
|
+
|
|
600
|
+
```python
|
|
601
|
+
# Upload a file
|
|
602
|
+
result = confluence.attach_file(
|
|
603
|
+
filename='/path/to/document.pdf',
|
|
604
|
+
page_id='123456789',
|
|
605
|
+
title='Document', # Optional: display name
|
|
606
|
+
comment='Uploaded via API'
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
print(f"Uploaded: {result['title']}")
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Attach File from Content
|
|
613
|
+
|
|
614
|
+
```python
|
|
615
|
+
# Upload file from bytes or file object
|
|
616
|
+
with open('/path/to/image.png', 'rb') as f:
|
|
617
|
+
result = confluence.attach_content(
|
|
618
|
+
content=f.read(),
|
|
619
|
+
name='screenshot.png',
|
|
620
|
+
content_type='image/png',
|
|
621
|
+
page_id='123456789',
|
|
622
|
+
comment='Screenshot of the dashboard'
|
|
623
|
+
)
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### Update Existing Attachment
|
|
627
|
+
|
|
628
|
+
```python
|
|
629
|
+
# Re-upload with same filename updates the attachment
|
|
630
|
+
result = confluence.attach_file(
|
|
631
|
+
filename='/path/to/updated-document.pdf',
|
|
632
|
+
page_id='123456789',
|
|
633
|
+
title='Document',
|
|
634
|
+
comment='Updated version'
|
|
635
|
+
)
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Download Attachment
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
# Get attachment info first
|
|
642
|
+
attachments = confluence.get_attachments_from_content(page_id='123456789')
|
|
643
|
+
|
|
644
|
+
for attachment in attachments.get('results', []):
|
|
645
|
+
if attachment['title'] == 'document.pdf':
|
|
646
|
+
download_link = attachment['_links']['download']
|
|
647
|
+
|
|
648
|
+
# Download the file
|
|
649
|
+
import requests
|
|
650
|
+
response = requests.get(
|
|
651
|
+
f"{confluence.url}{download_link}",
|
|
652
|
+
auth=(confluence.username, confluence.password)
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
with open('downloaded-document.pdf', 'wb') as f:
|
|
656
|
+
f.write(response.content)
|
|
657
|
+
|
|
658
|
+
break
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Delete Attachment
|
|
662
|
+
|
|
663
|
+
```python
|
|
664
|
+
confluence.delete_attachment(
|
|
665
|
+
page_id='123456789',
|
|
666
|
+
filename='old-document.pdf'
|
|
667
|
+
)
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Labels API
|
|
671
|
+
|
|
672
|
+
### Get Labels for Content
|
|
673
|
+
|
|
674
|
+
```python
|
|
675
|
+
labels = confluence.get_page_labels(page_id='123456789')
|
|
676
|
+
|
|
677
|
+
for label in labels.get('results', []):
|
|
678
|
+
print(f"Label: {label['name']}")
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Add Label to Content
|
|
682
|
+
|
|
683
|
+
```python
|
|
684
|
+
confluence.add_label(
|
|
685
|
+
page_id='123456789',
|
|
686
|
+
label='documentation'
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
print("Label added successfully")
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Add Multiple Labels
|
|
693
|
+
|
|
694
|
+
```python
|
|
695
|
+
labels = ['documentation', 'getting-started', 'tutorial']
|
|
696
|
+
|
|
697
|
+
for label in labels:
|
|
698
|
+
confluence.add_label(
|
|
699
|
+
page_id='123456789',
|
|
700
|
+
label=label
|
|
701
|
+
)
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### Remove Label from Content
|
|
705
|
+
|
|
706
|
+
```python
|
|
707
|
+
confluence.remove_label(
|
|
708
|
+
page_id='123456789',
|
|
709
|
+
label='old-label'
|
|
710
|
+
)
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Search Pages by Label
|
|
714
|
+
|
|
715
|
+
```python
|
|
716
|
+
results = confluence.cql('type=page AND label="documentation"')
|
|
717
|
+
|
|
718
|
+
for result in results.get('results', []):
|
|
719
|
+
content = result.get('content', {})
|
|
720
|
+
print(f"{content.get('title')} - {content.get('id')}")
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
## Comments API
|
|
724
|
+
|
|
725
|
+
### Get Comments for Page
|
|
726
|
+
|
|
727
|
+
```python
|
|
728
|
+
comments = confluence.get_page_comments(
|
|
729
|
+
page_id='123456789',
|
|
730
|
+
expand='body.storage',
|
|
731
|
+
depth='all'
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
for comment in comments:
|
|
735
|
+
print(f"Comment: {comment['title']}")
|
|
736
|
+
print(f"Content: {comment['body']['storage']['value']}")
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Add Comment to Page
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
comment = confluence.create_page(
|
|
743
|
+
space='DEMO',
|
|
744
|
+
title='Re: Page Title', # Comment title
|
|
745
|
+
body='<p>This is a helpful comment!</p>',
|
|
746
|
+
parent_id='123456789',
|
|
747
|
+
type='comment'
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
print(f"Comment added: {comment['id']}")
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
## User and Group APIs
|
|
754
|
+
|
|
755
|
+
### Get Current User
|
|
756
|
+
|
|
757
|
+
```python
|
|
758
|
+
user = confluence.get_current_user()
|
|
759
|
+
|
|
760
|
+
print(f"Display Name: {user['displayName']}")
|
|
761
|
+
print(f"Email: {user['email']}")
|
|
762
|
+
print(f"Account ID: {user['accountId']}")
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### Get User by Username (Server/Data Center)
|
|
766
|
+
|
|
767
|
+
```python
|
|
768
|
+
user = confluence.get_user_details_by_username(
|
|
769
|
+
username='john.doe'
|
|
770
|
+
)
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Get User by Account ID (Cloud)
|
|
774
|
+
|
|
775
|
+
```python
|
|
776
|
+
user = confluence.get_user_details_by_accountid(
|
|
777
|
+
account_id='5a1234567890123456789012'
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
print(f"Display Name: {user['displayName']}")
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### Get Group Members
|
|
784
|
+
|
|
785
|
+
```python
|
|
786
|
+
members = confluence.get_group_members(
|
|
787
|
+
group_name='confluence-administrators',
|
|
788
|
+
start=0,
|
|
789
|
+
limit=50
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
for member in members:
|
|
793
|
+
print(f"Member: {member['displayName']}")
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
### Check if User is in Group
|
|
797
|
+
|
|
798
|
+
```python
|
|
799
|
+
# Get all group members and check
|
|
800
|
+
members = confluence.get_group_members('confluence-users')
|
|
801
|
+
user_ids = [m['accountId'] for m in members]
|
|
802
|
+
|
|
803
|
+
is_member = 'user-account-id' in user_ids
|
|
804
|
+
print(f"User is member: {is_member}")
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
## Content Properties (Metadata)
|
|
808
|
+
|
|
809
|
+
### Get Content Properties
|
|
810
|
+
|
|
811
|
+
```python
|
|
812
|
+
properties = confluence.get_page_properties(page_id='123456789')
|
|
813
|
+
|
|
814
|
+
for prop in properties:
|
|
815
|
+
print(f"Key: {prop['key']}")
|
|
816
|
+
print(f"Value: {prop['value']}")
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### Get Specific Content Property
|
|
820
|
+
|
|
821
|
+
```python
|
|
822
|
+
property_value = confluence.get_page_property(
|
|
823
|
+
page_id='123456789',
|
|
824
|
+
page_property_key='custom-metadata'
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
print(f"Property value: {property_value}")
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### Set Content Property
|
|
831
|
+
|
|
832
|
+
```python
|
|
833
|
+
confluence.set_page_property(
|
|
834
|
+
page_id='123456789',
|
|
835
|
+
data={
|
|
836
|
+
'key': 'custom-metadata',
|
|
837
|
+
'value': {
|
|
838
|
+
'lastReviewed': '2025-11-07',
|
|
839
|
+
'reviewer': 'john.doe@example.com',
|
|
840
|
+
'status': 'approved'
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
### Update Content Property
|
|
847
|
+
|
|
848
|
+
```python
|
|
849
|
+
# Get current property
|
|
850
|
+
current = confluence.get_page_property(
|
|
851
|
+
page_id='123456789',
|
|
852
|
+
page_property_key='custom-metadata'
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
# Update with new data
|
|
856
|
+
current['value']['status'] = 'needs-review'
|
|
857
|
+
current['version']['number'] += 1
|
|
858
|
+
|
|
859
|
+
confluence.update_page_property(
|
|
860
|
+
page_id='123456789',
|
|
861
|
+
page_property_key='custom-metadata',
|
|
862
|
+
data=current
|
|
863
|
+
)
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### Delete Content Property
|
|
867
|
+
|
|
868
|
+
```python
|
|
869
|
+
confluence.delete_page_property(
|
|
870
|
+
page_id='123456789',
|
|
871
|
+
page_property_key='custom-metadata'
|
|
872
|
+
)
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
## Advanced Page Operations
|
|
876
|
+
|
|
877
|
+
### Copy a Page
|
|
878
|
+
|
|
879
|
+
```python
|
|
880
|
+
# Get source page
|
|
881
|
+
source = confluence.get_page_by_id(
|
|
882
|
+
page_id='123456789',
|
|
883
|
+
expand='body.storage,space'
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
# Create copy
|
|
887
|
+
copy = confluence.create_page(
|
|
888
|
+
space=source['space']['key'],
|
|
889
|
+
title=f"{source['title']} (Copy)",
|
|
890
|
+
body=source['body']['storage']['value'],
|
|
891
|
+
parent_id=None
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
print(f"Created copy: {copy['id']}")
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
### Copy Page with Children
|
|
898
|
+
|
|
899
|
+
```python
|
|
900
|
+
def copy_page_tree(confluence, source_page_id, target_space, parent_id=None):
|
|
901
|
+
"""Recursively copy a page and all its children."""
|
|
902
|
+
# Get source page
|
|
903
|
+
source = confluence.get_page_by_id(
|
|
904
|
+
page_id=source_page_id,
|
|
905
|
+
expand='body.storage,space'
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
# Create copy
|
|
909
|
+
copy = confluence.create_page(
|
|
910
|
+
space=target_space,
|
|
911
|
+
title=source['title'],
|
|
912
|
+
body=source['body']['storage']['value'],
|
|
913
|
+
parent_id=parent_id
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
# Copy children
|
|
917
|
+
children = confluence.get_page_child_by_type(
|
|
918
|
+
page_id=source_page_id,
|
|
919
|
+
type='page'
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
for child in children:
|
|
923
|
+
copy_page_tree(confluence, child['id'], target_space, copy['id'])
|
|
924
|
+
|
|
925
|
+
return copy
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### Move Page to Different Parent
|
|
929
|
+
|
|
930
|
+
```python
|
|
931
|
+
# Get current page
|
|
932
|
+
page = confluence.get_page_by_id(
|
|
933
|
+
page_id='123456789',
|
|
934
|
+
expand='version,body.storage,space,ancestors'
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
# Update with new parent
|
|
938
|
+
confluence.update_page(
|
|
939
|
+
page_id='123456789',
|
|
940
|
+
title=page['title'],
|
|
941
|
+
body=page['body']['storage']['value'],
|
|
942
|
+
parent_id='new-parent-id',
|
|
943
|
+
version_number=page['version']['number'] + 1
|
|
944
|
+
)
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### Export Page as PDF
|
|
948
|
+
|
|
949
|
+
```python
|
|
950
|
+
# Get page content in export view
|
|
951
|
+
page = confluence.get_page_by_id(
|
|
952
|
+
page_id='123456789',
|
|
953
|
+
expand='body.export_view'
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
export_html = page['body']['export_view']['value']
|
|
957
|
+
|
|
958
|
+
# Use a PDF library to convert HTML to PDF
|
|
959
|
+
# Example with pdfkit (requires wkhtmltopdf)
|
|
960
|
+
import pdfkit
|
|
961
|
+
pdfkit.from_string(export_html, 'output.pdf')
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
## Content Restrictions (Permissions)
|
|
965
|
+
|
|
966
|
+
### Get Content Restrictions
|
|
967
|
+
|
|
968
|
+
```python
|
|
969
|
+
restrictions = confluence.get_content_restrictions(
|
|
970
|
+
content_id='123456789',
|
|
971
|
+
expand='read.restrictions.user,update.restrictions.user'
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
print("Read restrictions:", restrictions.get('read'))
|
|
975
|
+
print("Update restrictions:", restrictions.get('update'))
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
### Add Read Restriction
|
|
979
|
+
|
|
980
|
+
```python
|
|
981
|
+
confluence.add_content_restriction(
|
|
982
|
+
content_id='123456789',
|
|
983
|
+
operation='read',
|
|
984
|
+
restrictions={
|
|
985
|
+
'user': [
|
|
986
|
+
{'accountId': '5a1234567890123456789012'}
|
|
987
|
+
],
|
|
988
|
+
'group': [
|
|
989
|
+
{'name': 'confluence-users'}
|
|
990
|
+
]
|
|
991
|
+
}
|
|
992
|
+
)
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### Remove Content Restrictions
|
|
996
|
+
|
|
997
|
+
```python
|
|
998
|
+
confluence.remove_content_restriction(
|
|
999
|
+
content_id='123456789',
|
|
1000
|
+
operation='read'
|
|
1001
|
+
)
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
## Macros in Content
|
|
1005
|
+
|
|
1006
|
+
### Table of Contents Macro
|
|
1007
|
+
|
|
1008
|
+
```python
|
|
1009
|
+
content = '''
|
|
1010
|
+
<h1>Table of Contents</h1>
|
|
1011
|
+
<ac:structured-macro ac:name="toc" ac:schema-version="1">
|
|
1012
|
+
<ac:parameter ac:name="maxLevel">3</ac:parameter>
|
|
1013
|
+
</ac:structured-macro>
|
|
1014
|
+
<h2>Section 1</h2>
|
|
1015
|
+
<p>Content here...</p>
|
|
1016
|
+
'''
|
|
1017
|
+
|
|
1018
|
+
confluence.create_page(
|
|
1019
|
+
space='DEMO',
|
|
1020
|
+
title='Documentation Index',
|
|
1021
|
+
body=content
|
|
1022
|
+
)
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
### Status Macro
|
|
1026
|
+
|
|
1027
|
+
```python
|
|
1028
|
+
content = '''
|
|
1029
|
+
<p>Project status:
|
|
1030
|
+
<ac:structured-macro ac:name="status" ac:schema-version="1">
|
|
1031
|
+
<ac:parameter ac:name="colour">Green</ac:parameter>
|
|
1032
|
+
<ac:parameter ac:name="title">Active</ac:parameter>
|
|
1033
|
+
</ac:structured-macro>
|
|
1034
|
+
</p>
|
|
1035
|
+
'''
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### Code Block Macro
|
|
1039
|
+
|
|
1040
|
+
```python
|
|
1041
|
+
content = '''
|
|
1042
|
+
<ac:structured-macro ac:name="code" ac:schema-version="1">
|
|
1043
|
+
<ac:parameter ac:name="language">python</ac:parameter>
|
|
1044
|
+
<ac:plain-text-body><![CDATA[
|
|
1045
|
+
def greet(name):
|
|
1046
|
+
return f"Hello, {name}!"
|
|
1047
|
+
|
|
1048
|
+
print(greet("World"))
|
|
1049
|
+
]]></ac:plain-text-body>
|
|
1050
|
+
</ac:structured-macro>
|
|
1051
|
+
'''
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
### Info Panel Macro
|
|
1055
|
+
|
|
1056
|
+
```python
|
|
1057
|
+
content = '''
|
|
1058
|
+
<ac:structured-macro ac:name="info" ac:schema-version="1">
|
|
1059
|
+
<ac:rich-text-body>
|
|
1060
|
+
<p>This is important information for users to know.</p>
|
|
1061
|
+
</ac:rich-text-body>
|
|
1062
|
+
</ac:structured-macro>
|
|
1063
|
+
'''
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
### Warning Panel Macro
|
|
1067
|
+
|
|
1068
|
+
```python
|
|
1069
|
+
content = '''
|
|
1070
|
+
<ac:structured-macro ac:name="warning" ac:schema-version="1">
|
|
1071
|
+
<ac:rich-text-body>
|
|
1072
|
+
<p><strong>Warning:</strong> This action cannot be undone.</p>
|
|
1073
|
+
</ac:rich-text-body>
|
|
1074
|
+
</ac:structured-macro>
|
|
1075
|
+
'''
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
### Note Panel Macro
|
|
1079
|
+
|
|
1080
|
+
```python
|
|
1081
|
+
content = '''
|
|
1082
|
+
<ac:structured-macro ac:name="note" ac:schema-version="1">
|
|
1083
|
+
<ac:rich-text-body>
|
|
1084
|
+
<p>Remember to save your work frequently.</p>
|
|
1085
|
+
</ac:rich-text-body>
|
|
1086
|
+
</ac:structured-macro>
|
|
1087
|
+
'''
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
### Excerpt Macro
|
|
1091
|
+
|
|
1092
|
+
```python
|
|
1093
|
+
content = '''
|
|
1094
|
+
<ac:structured-macro ac:name="excerpt" ac:schema-version="1">
|
|
1095
|
+
<ac:rich-text-body>
|
|
1096
|
+
<p>This is an excerpt that can be included in other pages.</p>
|
|
1097
|
+
</ac:rich-text-body>
|
|
1098
|
+
</ac:structured-macro>
|
|
1099
|
+
'''
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
### Include Page Macro
|
|
1103
|
+
|
|
1104
|
+
```python
|
|
1105
|
+
content = '''
|
|
1106
|
+
<ac:structured-macro ac:name="include" ac:schema-version="1">
|
|
1107
|
+
<ac:parameter ac:name=""><ac:link>
|
|
1108
|
+
<ri:page ri:content-title="Page to Include"/>
|
|
1109
|
+
</ac:link></ac:parameter>
|
|
1110
|
+
</ac:structured-macro>
|
|
1111
|
+
'''
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
## Error Handling
|
|
1115
|
+
|
|
1116
|
+
### Basic Error Handling
|
|
1117
|
+
|
|
1118
|
+
```python
|
|
1119
|
+
from atlassian import Confluence
|
|
1120
|
+
from requests.exceptions import HTTPError
|
|
1121
|
+
|
|
1122
|
+
try:
|
|
1123
|
+
page = confluence.get_page_by_id(page_id='invalid-id')
|
|
1124
|
+
except HTTPError as e:
|
|
1125
|
+
print(f"HTTP Error: {e.response.status_code}")
|
|
1126
|
+
print(f"Message: {e.response.text}")
|
|
1127
|
+
except Exception as e:
|
|
1128
|
+
print(f"Error: {str(e)}")
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
### Common HTTP Status Codes
|
|
1132
|
+
|
|
1133
|
+
- `400 Bad Request`: Invalid parameters or request body
|
|
1134
|
+
- `401 Unauthorized`: Invalid or missing authentication
|
|
1135
|
+
- `403 Forbidden`: Insufficient permissions
|
|
1136
|
+
- `404 Not Found`: Content or space does not exist
|
|
1137
|
+
- `409 Conflict`: Version conflict (update with wrong version number)
|
|
1138
|
+
- `429 Too Many Requests`: Rate limit exceeded
|
|
1139
|
+
|
|
1140
|
+
### Handling Version Conflicts
|
|
1141
|
+
|
|
1142
|
+
```python
|
|
1143
|
+
def update_page_with_retry(confluence, page_id, new_content, max_retries=3):
|
|
1144
|
+
"""Update page with automatic retry on version conflicts."""
|
|
1145
|
+
import time
|
|
1146
|
+
|
|
1147
|
+
for attempt in range(max_retries):
|
|
1148
|
+
try:
|
|
1149
|
+
# Get current version
|
|
1150
|
+
page = confluence.get_page_by_id(
|
|
1151
|
+
page_id=page_id,
|
|
1152
|
+
expand='version,body.storage'
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
# Update with incremented version
|
|
1156
|
+
updated = confluence.update_page(
|
|
1157
|
+
page_id=page_id,
|
|
1158
|
+
title=page['title'],
|
|
1159
|
+
body=new_content,
|
|
1160
|
+
version_number=page['version']['number'] + 1
|
|
1161
|
+
)
|
|
1162
|
+
|
|
1163
|
+
return updated
|
|
1164
|
+
|
|
1165
|
+
except HTTPError as e:
|
|
1166
|
+
if e.response.status_code == 409 and attempt < max_retries - 1:
|
|
1167
|
+
print(f"Conflict detected, retrying ({attempt + 1}/{max_retries})...")
|
|
1168
|
+
time.sleep(1)
|
|
1169
|
+
continue
|
|
1170
|
+
raise
|
|
1171
|
+
|
|
1172
|
+
raise Exception(f"Failed after {max_retries} retries")
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
### Rate Limit Handling
|
|
1176
|
+
|
|
1177
|
+
```python
|
|
1178
|
+
import time
|
|
1179
|
+
from requests.exceptions import HTTPError
|
|
1180
|
+
|
|
1181
|
+
def api_call_with_backoff(api_call, *args, max_retries=5, **kwargs):
|
|
1182
|
+
"""Execute API call with exponential backoff on rate limits."""
|
|
1183
|
+
for attempt in range(max_retries):
|
|
1184
|
+
try:
|
|
1185
|
+
return api_call(*args, **kwargs)
|
|
1186
|
+
except HTTPError as e:
|
|
1187
|
+
if e.response.status_code == 429 and attempt < max_retries - 1:
|
|
1188
|
+
retry_after = int(e.response.headers.get('Retry-After', 2 ** attempt))
|
|
1189
|
+
print(f"Rate limited, waiting {retry_after}s before retry...")
|
|
1190
|
+
time.sleep(retry_after)
|
|
1191
|
+
continue
|
|
1192
|
+
raise
|
|
1193
|
+
|
|
1194
|
+
raise Exception(f"Max retries ({max_retries}) exceeded")
|
|
1195
|
+
|
|
1196
|
+
# Usage
|
|
1197
|
+
page = api_call_with_backoff(
|
|
1198
|
+
confluence.get_page_by_id,
|
|
1199
|
+
page_id='123456789'
|
|
1200
|
+
)
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
### Comprehensive Error Handler
|
|
1204
|
+
|
|
1205
|
+
```python
|
|
1206
|
+
from requests.exceptions import HTTPError, Timeout, ConnectionError
|
|
1207
|
+
|
|
1208
|
+
def safe_api_call(func, *args, **kwargs):
|
|
1209
|
+
"""Wrapper for safe API calls with comprehensive error handling."""
|
|
1210
|
+
try:
|
|
1211
|
+
return func(*args, **kwargs)
|
|
1212
|
+
except HTTPError as e:
|
|
1213
|
+
status = e.response.status_code
|
|
1214
|
+
if status == 401:
|
|
1215
|
+
print("Authentication failed. Check your API token.")
|
|
1216
|
+
elif status == 403:
|
|
1217
|
+
print("Permission denied. Check user permissions.")
|
|
1218
|
+
elif status == 404:
|
|
1219
|
+
print("Resource not found.")
|
|
1220
|
+
elif status == 409:
|
|
1221
|
+
print("Version conflict. Refresh and try again.")
|
|
1222
|
+
elif status == 429:
|
|
1223
|
+
print("Rate limit exceeded. Slow down requests.")
|
|
1224
|
+
else:
|
|
1225
|
+
print(f"HTTP Error {status}: {e.response.text}")
|
|
1226
|
+
raise
|
|
1227
|
+
except Timeout:
|
|
1228
|
+
print("Request timed out. Try again later.")
|
|
1229
|
+
raise
|
|
1230
|
+
except ConnectionError:
|
|
1231
|
+
print("Connection error. Check network connectivity.")
|
|
1232
|
+
raise
|
|
1233
|
+
except Exception as e:
|
|
1234
|
+
print(f"Unexpected error: {str(e)}")
|
|
1235
|
+
raise
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
## Bulk Operations
|
|
1239
|
+
|
|
1240
|
+
### Get Multiple Pages by IDs
|
|
1241
|
+
|
|
1242
|
+
```python
|
|
1243
|
+
def get_pages_by_ids(confluence, page_ids):
|
|
1244
|
+
"""Fetch multiple pages by their IDs."""
|
|
1245
|
+
pages = []
|
|
1246
|
+
|
|
1247
|
+
for page_id in page_ids:
|
|
1248
|
+
try:
|
|
1249
|
+
page = confluence.get_page_by_id(
|
|
1250
|
+
page_id=page_id,
|
|
1251
|
+
expand='body.storage,version'
|
|
1252
|
+
)
|
|
1253
|
+
pages.append(page)
|
|
1254
|
+
except HTTPError as e:
|
|
1255
|
+
print(f"Failed to fetch page {page_id}: {e}")
|
|
1256
|
+
|
|
1257
|
+
return pages
|
|
1258
|
+
```
|
|
1259
|
+
|
|
1260
|
+
### Batch Create Pages
|
|
1261
|
+
|
|
1262
|
+
```python
|
|
1263
|
+
import time
|
|
1264
|
+
|
|
1265
|
+
def create_multiple_pages(confluence, space_key, page_data):
|
|
1266
|
+
"""Create multiple pages in a space."""
|
|
1267
|
+
created = []
|
|
1268
|
+
|
|
1269
|
+
for data in page_data:
|
|
1270
|
+
page = confluence.create_page(
|
|
1271
|
+
space=space_key,
|
|
1272
|
+
title=data['title'],
|
|
1273
|
+
body=data['content'],
|
|
1274
|
+
parent_id=data.get('parent_id')
|
|
1275
|
+
)
|
|
1276
|
+
created.append(page)
|
|
1277
|
+
|
|
1278
|
+
# Rate limit protection: wait between requests
|
|
1279
|
+
time.sleep(0.2)
|
|
1280
|
+
|
|
1281
|
+
return created
|
|
1282
|
+
|
|
1283
|
+
# Usage
|
|
1284
|
+
pages = [
|
|
1285
|
+
{'title': 'Page 1', 'content': '<p>Content 1</p>'},
|
|
1286
|
+
{'title': 'Page 2', 'content': '<p>Content 2</p>'},
|
|
1287
|
+
{'title': 'Page 3', 'content': '<p>Content 3</p>'},
|
|
1288
|
+
]
|
|
1289
|
+
|
|
1290
|
+
created_pages = create_multiple_pages(confluence, 'DEMO', pages)
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
### Batch Update Pages
|
|
1294
|
+
|
|
1295
|
+
```python
|
|
1296
|
+
def update_multiple_pages(confluence, updates):
|
|
1297
|
+
"""Update multiple pages."""
|
|
1298
|
+
results = []
|
|
1299
|
+
|
|
1300
|
+
for update in updates:
|
|
1301
|
+
page = confluence.get_page_by_id(
|
|
1302
|
+
page_id=update['id'],
|
|
1303
|
+
expand='version,body.storage'
|
|
1304
|
+
)
|
|
1305
|
+
|
|
1306
|
+
updated = confluence.update_page(
|
|
1307
|
+
page_id=update['id'],
|
|
1308
|
+
title=update.get('title', page['title']),
|
|
1309
|
+
body=update['content'],
|
|
1310
|
+
version_number=page['version']['number'] + 1
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
results.append(updated)
|
|
1314
|
+
time.sleep(0.2)
|
|
1315
|
+
|
|
1316
|
+
return results
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
### Export All Pages from Space
|
|
1320
|
+
|
|
1321
|
+
```python
|
|
1322
|
+
import json
|
|
1323
|
+
|
|
1324
|
+
def export_space_to_json(confluence, space_key, output_file):
|
|
1325
|
+
"""Export all pages from a space to JSON file."""
|
|
1326
|
+
pages_data = []
|
|
1327
|
+
|
|
1328
|
+
for page in confluence.get_all_pages_from_space_as_generator(
|
|
1329
|
+
space=space_key,
|
|
1330
|
+
expand='body.storage,version'
|
|
1331
|
+
):
|
|
1332
|
+
pages_data.append({
|
|
1333
|
+
'id': page['id'],
|
|
1334
|
+
'title': page['title'],
|
|
1335
|
+
'content': page['body']['storage']['value'],
|
|
1336
|
+
'version': page['version']['number']
|
|
1337
|
+
})
|
|
1338
|
+
|
|
1339
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
1340
|
+
json.dump(pages_data, f, indent=2, ensure_ascii=False)
|
|
1341
|
+
|
|
1342
|
+
print(f"Exported {len(pages_data)} pages to {output_file}")
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
### Clone Space to Another Space
|
|
1346
|
+
|
|
1347
|
+
```python
|
|
1348
|
+
def clone_space(confluence, source_space, target_space):
|
|
1349
|
+
"""Clone all pages from source space to target space."""
|
|
1350
|
+
# Get all pages from source
|
|
1351
|
+
for page in confluence.get_all_pages_from_space_as_generator(
|
|
1352
|
+
space=source_space,
|
|
1353
|
+
expand='body.storage'
|
|
1354
|
+
):
|
|
1355
|
+
try:
|
|
1356
|
+
confluence.create_page(
|
|
1357
|
+
space=target_space,
|
|
1358
|
+
title=page['title'],
|
|
1359
|
+
body=page['body']['storage']['value']
|
|
1360
|
+
)
|
|
1361
|
+
print(f"Cloned: {page['title']}")
|
|
1362
|
+
time.sleep(0.2)
|
|
1363
|
+
except Exception as e:
|
|
1364
|
+
print(f"Failed to clone {page['title']}: {e}")
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
## Performance and Optimization
|
|
1368
|
+
|
|
1369
|
+
### Use Expand Parameter Wisely
|
|
1370
|
+
|
|
1371
|
+
```python
|
|
1372
|
+
# Bad - expands everything (slow)
|
|
1373
|
+
page = confluence.get_page_by_id(
|
|
1374
|
+
page_id='123456789',
|
|
1375
|
+
expand='body.storage,body.view,body.editor,version,space,history,ancestors,descendants,container'
|
|
1376
|
+
)
|
|
1377
|
+
|
|
1378
|
+
# Good - only what you need (fast)
|
|
1379
|
+
page = confluence.get_page_by_id(
|
|
1380
|
+
page_id='123456789',
|
|
1381
|
+
expand='body.storage,version'
|
|
1382
|
+
)
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
### Pagination Best Practices
|
|
1386
|
+
|
|
1387
|
+
```python
|
|
1388
|
+
def get_all_pages_paginated(confluence, space_key, batch_size=100):
|
|
1389
|
+
"""Get all pages with manual pagination control."""
|
|
1390
|
+
all_pages = []
|
|
1391
|
+
start = 0
|
|
1392
|
+
|
|
1393
|
+
while True:
|
|
1394
|
+
batch = confluence.get_all_pages_from_space(
|
|
1395
|
+
space=space_key,
|
|
1396
|
+
start=start,
|
|
1397
|
+
limit=batch_size
|
|
1398
|
+
)
|
|
1399
|
+
|
|
1400
|
+
if not batch:
|
|
1401
|
+
break
|
|
1402
|
+
|
|
1403
|
+
all_pages.extend(batch)
|
|
1404
|
+
|
|
1405
|
+
if len(batch) < batch_size:
|
|
1406
|
+
break
|
|
1407
|
+
|
|
1408
|
+
start += batch_size
|
|
1409
|
+
|
|
1410
|
+
return all_pages
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
### Use Generators for Large Datasets
|
|
1414
|
+
|
|
1415
|
+
```python
|
|
1416
|
+
# Memory efficient - processes one page at a time
|
|
1417
|
+
def process_all_pages(confluence, space_key):
|
|
1418
|
+
for page in confluence.get_all_pages_from_space_as_generator(space=space_key):
|
|
1419
|
+
# Process each page
|
|
1420
|
+
process_page(page)
|
|
1421
|
+
# Memory is released after each iteration
|
|
1422
|
+
|
|
1423
|
+
# Memory inefficient - loads all pages into memory
|
|
1424
|
+
def process_all_pages_bad(confluence, space_key):
|
|
1425
|
+
all_pages = confluence.get_all_pages_from_space(space=space_key, limit=10000)
|
|
1426
|
+
for page in all_pages:
|
|
1427
|
+
process_page(page)
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
### Caching Strategies
|
|
1431
|
+
|
|
1432
|
+
```python
|
|
1433
|
+
from functools import lru_cache
|
|
1434
|
+
import time
|
|
1435
|
+
|
|
1436
|
+
class CachedConfluence:
|
|
1437
|
+
def __init__(self, confluence):
|
|
1438
|
+
self.confluence = confluence
|
|
1439
|
+
self.cache = {}
|
|
1440
|
+
self.cache_ttl = 300 # 5 minutes
|
|
1441
|
+
|
|
1442
|
+
def get_page_cached(self, page_id):
|
|
1443
|
+
"""Get page with caching."""
|
|
1444
|
+
cache_key = f"page_{page_id}"
|
|
1445
|
+
|
|
1446
|
+
if cache_key in self.cache:
|
|
1447
|
+
cached_data, cached_time = self.cache[cache_key]
|
|
1448
|
+
if time.time() - cached_time < self.cache_ttl:
|
|
1449
|
+
return cached_data
|
|
1450
|
+
|
|
1451
|
+
# Fetch fresh data
|
|
1452
|
+
page = self.confluence.get_page_by_id(page_id=page_id)
|
|
1453
|
+
self.cache[cache_key] = (page, time.time())
|
|
1454
|
+
|
|
1455
|
+
return page
|
|
1456
|
+
|
|
1457
|
+
def invalidate_cache(self, page_id=None):
|
|
1458
|
+
"""Invalidate cache for specific page or all."""
|
|
1459
|
+
if page_id:
|
|
1460
|
+
cache_key = f"page_{page_id}"
|
|
1461
|
+
self.cache.pop(cache_key, None)
|
|
1462
|
+
else:
|
|
1463
|
+
self.cache.clear()
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
## Common Patterns
|
|
1467
|
+
|
|
1468
|
+
### Create or Update Page
|
|
1469
|
+
|
|
1470
|
+
```python
|
|
1471
|
+
def create_or_update_page(confluence, space_key, title, content):
|
|
1472
|
+
"""Create page if it doesn't exist, update if it does."""
|
|
1473
|
+
existing = confluence.get_page_by_title(
|
|
1474
|
+
space=space_key,
|
|
1475
|
+
title=title
|
|
1476
|
+
)
|
|
1477
|
+
|
|
1478
|
+
if existing:
|
|
1479
|
+
# Update existing page
|
|
1480
|
+
page = confluence.get_page_by_id(
|
|
1481
|
+
page_id=existing['id'],
|
|
1482
|
+
expand='version'
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
return confluence.update_page(
|
|
1486
|
+
page_id=existing['id'],
|
|
1487
|
+
title=title,
|
|
1488
|
+
body=content,
|
|
1489
|
+
version_number=page['version']['number'] + 1
|
|
1490
|
+
)
|
|
1491
|
+
else:
|
|
1492
|
+
# Create new page
|
|
1493
|
+
return confluence.create_page(
|
|
1494
|
+
space=space_key,
|
|
1495
|
+
title=title,
|
|
1496
|
+
body=content
|
|
1497
|
+
)
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
### Build Table of Contents
|
|
1501
|
+
|
|
1502
|
+
```python
|
|
1503
|
+
def build_table_of_contents(confluence, space_key):
|
|
1504
|
+
"""Generate HTML table of contents for all pages in space."""
|
|
1505
|
+
pages = confluence.get_all_pages_from_space(space=space_key)
|
|
1506
|
+
|
|
1507
|
+
toc_html = '<h2>Table of Contents</h2><ul>'
|
|
1508
|
+
|
|
1509
|
+
for page in pages:
|
|
1510
|
+
toc_html += f'''
|
|
1511
|
+
<li>
|
|
1512
|
+
<ac:link>
|
|
1513
|
+
<ri:page ri:content-title="{page['title']}"/>
|
|
1514
|
+
</ac:link>
|
|
1515
|
+
</li>
|
|
1516
|
+
'''
|
|
1517
|
+
|
|
1518
|
+
toc_html += '</ul>'
|
|
1519
|
+
|
|
1520
|
+
return toc_html
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
### Sync Local Files to Confluence
|
|
1524
|
+
|
|
1525
|
+
```python
|
|
1526
|
+
import os
|
|
1527
|
+
from pathlib import Path
|
|
1528
|
+
|
|
1529
|
+
def sync_markdown_to_confluence(confluence, directory, space_key, parent_id=None):
|
|
1530
|
+
"""Sync Markdown files to Confluence pages."""
|
|
1531
|
+
import markdown
|
|
1532
|
+
|
|
1533
|
+
for md_file in Path(directory).glob('*.md'):
|
|
1534
|
+
# Read Markdown file
|
|
1535
|
+
with open(md_file, 'r', encoding='utf-8') as f:
|
|
1536
|
+
md_content = f.read()
|
|
1537
|
+
|
|
1538
|
+
# Convert Markdown to HTML
|
|
1539
|
+
html_content = markdown.markdown(md_content)
|
|
1540
|
+
|
|
1541
|
+
# Extract title from filename
|
|
1542
|
+
title = md_file.stem.replace('-', ' ').title()
|
|
1543
|
+
|
|
1544
|
+
# Create or update page
|
|
1545
|
+
create_or_update_page(
|
|
1546
|
+
confluence,
|
|
1547
|
+
space_key=space_key,
|
|
1548
|
+
title=title,
|
|
1549
|
+
content=html_content
|
|
1550
|
+
)
|
|
1551
|
+
|
|
1552
|
+
print(f"Synced: {title}")
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
## Content Body Formats
|
|
1556
|
+
|
|
1557
|
+
Confluence supports multiple body representations:
|
|
1558
|
+
|
|
1559
|
+
```python
|
|
1560
|
+
# Get page with different body formats
|
|
1561
|
+
page = confluence.get_page_by_id(
|
|
1562
|
+
page_id='123456789',
|
|
1563
|
+
expand='body.storage,body.view,body.export_view'
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
# Storage format (XHTML for create/update)
|
|
1567
|
+
storage = page['body']['storage']['value']
|
|
1568
|
+
|
|
1569
|
+
# View format (HTML for rendering)
|
|
1570
|
+
view = page['body']['view']['value']
|
|
1571
|
+
|
|
1572
|
+
# Export format (HTML optimized for export)
|
|
1573
|
+
export = page['body']['export_view']['value']
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
## Common Mistakes to Avoid
|
|
1577
|
+
|
|
1578
|
+
- **Not incrementing version number** when updating (causes 409 conflict)
|
|
1579
|
+
- **Using password instead of API token** for Cloud (causes 401 error)
|
|
1580
|
+
- **Forgetting cloud=True** for Confluence Cloud instances
|
|
1581
|
+
- **Not using expand parameter** to get needed fields (results in missing data)
|
|
1582
|
+
- **Using storage format incorrectly** (must be valid XHTML)
|
|
1583
|
+
- **Not handling pagination** for large result sets (misses data beyond first page)
|
|
1584
|
+
- **Hardcoding credentials** in code instead of environment variables (security risk)
|
|
1585
|
+
- **Not implementing rate limiting** in bulk operations (causes 429 errors)
|
|
1586
|
+
- **Creating duplicate pages** without checking if title exists first
|
|
1587
|
+
- **Not escaping HTML** in content (causes malformed XML errors)
|
|
1588
|
+
- **Using get_all_pages_from_space** without limit for large spaces (memory issues)
|
|
1589
|
+
- **Not using generators** for processing large datasets (runs out of memory)
|
|
1590
|
+
- **Ignoring HTTP errors** without proper error handling (silent failures)
|
|
1591
|
+
- **Not validating page exists** before operations (causes 404 errors)
|
|
1592
|
+
|
|
1593
|
+
## Reference Links
|
|
1594
|
+
|
|
1595
|
+
- **atlassian-python-api Documentation**: https://atlassian-python-api.readthedocs.io/
|
|
1596
|
+
- **Confluence Module Docs**: https://atlassian-python-api.readthedocs.io/confluence.html
|
|
1597
|
+
- **Confluence Cloud REST API v2**: https://developer.atlassian.com/cloud/confluence/rest/v2/
|
|
1598
|
+
- **Confluence Cloud REST API v1**: https://developer.atlassian.com/cloud/confluence/rest/v1/
|
|
1599
|
+
- **CQL (Confluence Query Language)**: https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/
|
|
1600
|
+
- **Storage Format Guide**: https://confluence.atlassian.com/doc/confluence-storage-format-790796544.html
|
|
1601
|
+
- **Atlassian API Tokens**: https://id.atlassian.com/manage-profile/security/api-tokens
|
|
1602
|
+
- **Rate Limits**: https://developer.atlassian.com/cloud/confluence/rate-limiting/
|
|
1603
|
+
- **PyPI Package**: https://pypi.org/project/atlassian-python-api/
|
|
1604
|
+
- **GitHub Repository**: https://github.com/atlassian-api/atlassian-python-api
|