Python SDK for the Bitbucket Cloud REST API v2.0
Project description
bitbucket-sdk
Python SDK for the Bitbucket Cloud REST API v2.0.
Installation
pip install -e .
Authentication
Two authentication methods are supported.
Option 1 — Atlassian API token (most common)
export BITBUCKET_EMAIL="you@example.com"
export BITBUCKET_API_TOKEN="ATATxxxxxxxxxxxxxxx"
Generate a token at: https://id.atlassian.com/manage-profile/security/api-tokens
client = BitbucketClient() # reads env vars
client = BitbucketClient(email="you@example.com", api_token="...") # explicit
Option 2 — OAuth 2.0 / workspace access token (Bearer Auth)
Use this when you already hold an access token — e.g. from CI/CD, a Bitbucket workspace access token, or a completed OAuth flow.
export BITBUCKET_ACCESS_TOKEN="eyJxxxxxxxxxxxxxxx"
client = BitbucketClient() # reads BITBUCKET_ACCESS_TOKEN from env
client = BitbucketClient(access_token="eyJ...") # explicit
Auth resolution: if access_token (or BITBUCKET_ACCESS_TOKEN) is present it
takes precedence over API token credentials.
Quick start
from bitbucket_sdk import BitbucketClient
# Reads BITBUCKET_EMAIL and BITBUCKET_API_TOKEN from environment
client = BitbucketClient()
# Use as a context manager to close the connection pool cleanly on exit
with BitbucketClient() as client:
repos = client.repositories.list("myworkspace")
for repo in repos:
print(repo.full_name)
API Reference
BitbucketClient
client = BitbucketClient(
email=None, # falls back to BITBUCKET_EMAIL env var
api_token=None, # falls back to BITBUCKET_API_TOKEN env var
timeout=30, # HTTP request timeout in seconds
)
Supports use as a context manager (with BitbucketClient() as client:), which closes
the underlying connection pool on exit.
Sub-resources:
client.repositories→RepositoriesResourceclient.pull_requests→PullRequestsResource
client.repositories
list(workspace, updated_since=None) → PagedList[Repository]
List repositories in a workspace (first page only). Use list_all() for all pages.
repos = client.repositories.list("myworkspace")
repos = client.repositories.list("myworkspace", updated_since="2024-01-01")
| Parameter | Type | Required | Description |
|---|---|---|---|
workspace |
str |
yes | Workspace slug |
updated_since |
str |
no | ISO-8601 date — only return repos updated after this date |
list_all(workspace, updated_since=None) → Iterator[Repository]
Yield every repository across all pages automatically.
for repo in client.repositories.list_all("myworkspace"):
print(repo.full_name)
get_commit_diff(workspace, repo, commits) → str
Raw unified diff for a commit or commit range.
diff = client.repositories.get_commit_diff("myworkspace", "myrepo", "abc1234")
diff = client.repositories.get_commit_diff("myworkspace", "myrepo", "abc1234..def5678")
| Parameter | Type | Required | Description |
|---|---|---|---|
workspace |
str |
yes | Workspace slug |
repo |
str |
yes | Repository slug |
commits |
str |
yes | Single hash ("abc1234") or range ("abc..def") |
client.pull_requests
list(workspace, repo, state="OPEN") → PagedList[PullRequest]
List pull requests filtered by state (first page only). Use list_all() for all pages.
open_prs = client.pull_requests.list("myworkspace", "myrepo")
merged_prs = client.pull_requests.list("myworkspace", "myrepo", state="MERGED")
| Parameter | Type | Required | Description |
|---|---|---|---|
workspace |
str |
yes | Workspace slug |
repo |
str |
yes | Repository slug |
state |
str |
no | "OPEN" (default), "MERGED", "DECLINED", "SUPERSEDED" |
list_all(workspace, repo, state="OPEN") → Iterator[PullRequest]
Yield every pull request across all pages automatically.
for pr in client.pull_requests.list_all("myworkspace", "myrepo"):
print(pr.id, pr.title)
get_open(workspace, repo, branch=None) → PullRequest
Return a single open PR, optionally filtered by source branch name.
Raises NotFoundError if no match is found.
pr = client.pull_requests.get_open("myworkspace", "myrepo")
pr = client.pull_requests.get_open("myworkspace", "myrepo", branch="feature/my-thing")
get(workspace, repo, pr_id) → PullRequest
Fetch a single pull request by its numeric ID.
pr = client.pull_requests.get("myworkspace", "myrepo", 42)
print(pr.title, pr.state, pr.author.display_name)
get_diff(workspace, repo, pr_id) → str
Raw unified diff for a pull request.
diff = client.pull_requests.get_diff("myworkspace", "myrepo", 42)
get_diffstat(workspace, repo, pr_id) → PagedList[DiffStat]
Per-file change statistics for a pull request.
stats = client.pull_requests.get_diffstat("myworkspace", "myrepo", 42)
for stat in stats:
print(stat.new_path, f"+{stat.lines_added} -{stat.lines_removed}")
DiffStat fields: status, lines_added, lines_removed, old_path, new_path
list_comments(workspace, repo, pr_id) → PagedList[Comment]
List all comments on a PR (first page only). Use list_all_comments() for all pages.
comments = client.pull_requests.list_comments("myworkspace", "myrepo", 42)
list_all_comments(workspace, repo, pr_id) → Iterator[Comment]
Yield every comment across all pages automatically.
for comment in client.pull_requests.list_all_comments("myworkspace", "myrepo", 42):
print(comment.user.display_name, comment.content.raw)
list_unresolved_comments(workspace, repo, pr_id, inline_only=False) → list[Comment]
Return only unresolved comments. Fetches all pages internally.
unresolved = client.pull_requests.list_unresolved_comments("myworkspace", "myrepo", 42)
# Only unresolved inline (file-attached) comments
inline = client.pull_requests.list_unresolved_comments("myworkspace", "myrepo", 42, inline_only=True)
for c in inline:
print(c.inline.path, c.inline.to_line, c.content.raw)
| Parameter | Type | Required | Description |
|---|---|---|---|
inline_only |
bool |
no | If True, only return inline comments (default False) |
post_comment(workspace, repo, pr_id, body, file_path=None, line=None) → Comment
Post a comment on a PR. Provide file_path and line together for an inline comment.
# General comment
comment = client.pull_requests.post_comment("myworkspace", "myrepo", 42, "LGTM!")
# Inline comment on a specific file and line
comment = client.pull_requests.post_comment(
"myworkspace", "myrepo", 42,
body="This looks suspicious.",
file_path="src/auth.py",
line=17,
)
| Parameter | Type | Required | Description |
|---|---|---|---|
body |
str |
yes | Comment text (Markdown supported) |
file_path |
str |
no | File path — must be paired with line |
line |
int |
no | Line number (1-based) — must be paired with file_path |
resolve_comment(workspace, repo, pr_id, comment_id) → None
Mark an inline comment thread as resolved.
client.pull_requests.resolve_comment("myworkspace", "myrepo", 42, comment_id=99)
approve(workspace, repo, pr_id) → None
Approve a PR as the authenticated user.
client.pull_requests.approve("myworkspace", "myrepo", 42)
unapprove(workspace, repo, pr_id) → None
Remove the authenticated user's approval from a PR.
client.pull_requests.unapprove("myworkspace", "myrepo", 42)
decline(workspace, repo, pr_id) → PullRequest
Decline a PR.
pr = client.pull_requests.decline("myworkspace", "myrepo", 42)
print(pr.state) # "DECLINED"
merge(workspace, repo, pr_id, strategy="merge_commit", close_source_branch=None, message=None) → PullRequest
Merge a PR.
pr = client.pull_requests.merge("myworkspace", "myrepo", 42)
pr = client.pull_requests.merge("myworkspace", "myrepo", 42, strategy="squash")
pr = client.pull_requests.merge("myworkspace", "myrepo", 42, close_source_branch=True, message="Done")
print(pr.state) # "MERGED"
| Parameter | Type | Required | Description |
|---|---|---|---|
strategy |
str |
no | "merge_commit" (default), "squash", "fast_forward" |
close_source_branch |
bool |
no | Delete source branch after merge. Defaults to the PR's own setting |
message |
str |
no | Custom merge commit message |
create(workspace, repo, title, source_branch, destination_branch, description=None, reviewers=None, close_source_branch=False) → PullRequest
Create a new pull request.
pr = client.pull_requests.create(
workspace="myworkspace",
repo="myrepo",
title="Add login page",
source_branch="feature/login",
destination_branch="main",
description="Implements the login flow from the spec.",
reviewers=["{uuid-abc}", "{uuid-def}"], # Bitbucket user UUIDs
close_source_branch=True,
)
print(pr.id, pr.title)
| Parameter | Type | Required | Description |
|---|---|---|---|
title |
str |
yes | PR title |
source_branch |
str |
yes | Branch containing the changes |
destination_branch |
str |
yes | Branch to merge into |
description |
str |
no | PR description (Markdown supported) |
reviewers |
list[str] |
no | List of reviewer UUIDs (find via Bitbucket UI or API) |
close_source_branch |
bool |
no | Delete source branch after merge (default False) |
Response Models
PullRequest
| Field | Type | Description |
|---|---|---|
id |
int |
PR number |
title |
str |
PR title |
description |
str |
PR description |
state |
str |
"OPEN", "MERGED", "DECLINED", "SUPERSEDED" |
author |
User |
Author details |
source |
Ref |
Source branch (source.branch_name) |
destination |
Ref |
Destination branch (destination.branch_name) |
comment_count |
int |
Total comments |
created_on |
datetime |
Creation timestamp |
updated_on |
datetime |
Last updated timestamp |
Comment
| Field | Type | Description |
|---|---|---|
id |
int |
Comment ID |
content |
Content |
Body (content.raw for Markdown text) |
user |
User |
Author |
inline |
Inline | None |
Inline location if applicable |
resolved |
bool |
Whether the thread is resolved |
is_inline |
bool |
Property — True if this is an inline comment |
Repository
| Field | Type | Description |
|---|---|---|
full_name |
str |
"workspace/repo" |
name |
str |
Repo slug |
uuid |
str |
Unique identifier |
scm |
str |
"git" |
PagedList[T]
Returned by all list methods. Iterable and supports len().
| Field | Type | Description |
|---|---|---|
values |
list[T] |
Items on this page |
size |
int |
Total items across all pages |
page |
int |
Current page number |
next |
str | None |
URL for the next page |
Use the corresponding list_all() / list_all_comments() methods to iterate all pages
without manually following next.
Error Handling
from bitbucket_sdk import (
BitbucketClient,
AuthenticationError,
NotFoundError,
PermissionError,
RateLimitError,
APIError,
BitbucketError,
)
try:
pr = client.pull_requests.get("myworkspace", "myrepo", 999)
except NotFoundError:
print("PR does not exist")
except AuthenticationError:
print("Invalid credentials — check BITBUCKET_EMAIL and BITBUCKET_API_TOKEN")
except PermissionError:
print("You do not have access to this resource")
except RateLimitError:
print("Rate limited — the SDK retries this automatically")
except APIError as e:
print(f"Unexpected API error: {e.status_code} {e}")
except BitbucketError as e:
print(f"SDK error: {e}")
Retry Behaviour
The SDK automatically retries transient failures using tenacity:
| Trigger | Behaviour |
|---|---|
| Network error (connection reset, timeout) | Retry with exponential backoff |
HTTP 429 with Retry-After header |
Wait the exact duration the server requests |
HTTP 429 without Retry-After |
Retry with exponential backoff |
| HTTP 5xx server errors | Retry with exponential backoff |
| HTTP 400 / 401 / 403 / 404 | Raise immediately — these are caller errors |
Policy: up to 3 attempts, backoff sequence 1s → 2s → 4s (capped at 10s).
Retry attempts are logged at WARNING level via Python's standard logging module.
To see retry logs:
import logging
logging.basicConfig(level=logging.WARNING)
Logging
The SDK logs at two levels using the bitbucket_sdk._http logger:
| Level | What is logged |
|---|---|
DEBUG |
Every request (--> GET /path) and response (<-- 200) |
WARNING |
Each retry attempt before sleeping |
import logging
logging.getLogger("bitbucket_sdk._http").setLevel(logging.DEBUG)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bitbucket_sdk-0.1.0.tar.gz.
File metadata
- Download URL: bitbucket_sdk-0.1.0.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e8e76537d8a2709670d32296a20e49c5eed112523cc6d771af1f583a59ebf62
|
|
| MD5 |
327ffdbed9d50bc8a8c3eaa415ef02c3
|
|
| BLAKE2b-256 |
33ab2d27c03b7bdc288f2a27ae0fae1d7545cbdf3184d11301a82c75c2e197d3
|
File details
Details for the file bitbucket_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: bitbucket_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8126033b0ea5fbf58695ff6a25301df7a7a9f3ec8ed1861e0315d3169e38b9c
|
|
| MD5 |
c54385b7e268b1676cb8fe5a12937460
|
|
| BLAKE2b-256 |
b80de28a726ba5f36685415a1ca23df74676f6c379a1038e14c7983aded97bbe
|