qodev-gitlab-api
A lightweight, typed Python client for the GitLab REST API. Built on httpx with automatic pagination, structured error handling, and .env support, it provides a clean interface for common GitLab operations without the weight of a full-featured SDK.
Why this library?
- Lightweight -- just
httpx, no heavy ORM-like abstractions - Typed -- ships with
py.typedfor full mypy/pyright support - Agent-friendly -- simple method signatures, dict returns, auto-pagination
- Built for tools -- designed for MCP servers and CLIs, not full application frameworks
- Focused --
python-gitlabis comprehensive but heavy; this library covers the operations AI agents and developer tools actually need
Installation
pip install qodev-gitlab-api
Quick Start
from qodev_gitlab_api import GitLabClient
# Reads GITLAB_TOKEN and GITLAB_URL from environment or .env file
client = GitLabClient()
# Or pass credentials explicitly
client = GitLabClient(token="glpat-xxxxxxxxxxxx", base_url="https://gitlab.example.com")
# Skip connection validation for faster startup
client = GitLabClient(validate=False)
# List open merge requests
mrs = client.get_merge_requests("mygroup/myproject", state="opened")
for mr in mrs:
print(f"!{mr['iid']} {mr['title']}")
# Create a merge request
client.create_merge_request("mygroup/myproject", "feature-branch", "main", "Add new feature")
# Get pipeline status
pipelines = client.get_pipelines("mygroup/myproject")
Features
- Merge requests -- create, update, merge, close, and review with inline diff comments
- Pipelines and jobs -- list, inspect, wait for completion, retry, and download artifacts
- Issues -- create, update, close, and comment
- Releases -- create, update, delete, and list with asset links
- CI/CD variables -- get, list, create, update, and upsert (set) project variables
- File operations -- read repository files at any ref, upload files for markdown embedding
- Automatic pagination -- all list endpoints handle multi-page results transparently
- Typed exceptions --
AuthenticationError,NotFoundError,APIError,ConfigurationError
Configuration
The client reads configuration from environment variables, with .env file support via python-dotenv:
| Variable | Description | Default |
|---|---|---|
GITLAB_TOKEN |
GitLab personal access token (required) | -- |
GITLAB_URL |
GitLab instance base URL | https://gitlab.com |
You can also use GITLAB_BASE_URL as an alias for GITLAB_URL.
Create a .env file in your project root:
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
GITLAB_URL=https://gitlab.example.com
API Reference
Projects
projects = client.get_projects(owned=True)
project = client.get_project("my-group/my-project")
Merge Requests
# List and inspect
mrs = client.get_merge_requests("my-group/my-project", state="opened")
mr = client.get_merge_request("my-group/my-project", mr_iid=42)
changes = client.get_mr_changes("my-group/my-project", mr_iid=42)
commits = client.get_mr_commits("my-group/my-project", mr_iid=42)
approvals = client.get_mr_approvals("my-group/my-project", mr_iid=42)
# Create
mr = client.create_merge_request(
"my-group/my-project",
source_branch="feature/foo",
target_branch="main",
title="Add foo feature",
description="Implements the foo feature.",
assignee_ids=[123],
reviewer_ids=[456],
labels="enhancement",
)
# Update, merge, close
client.update_mr("my-group/my-project", mr_iid=42, title="Updated title")
client.merge_mr("my-group/my-project", mr_iid=42, squash=True)
client.close_mr("my-group/my-project", mr_iid=42)
# Discussions and comments
discussions = client.get_mr_discussions("my-group/my-project", mr_iid=42)
client.create_mr_note("my-group/my-project", mr_iid=42, body="Looks good!")
client.reply_to_discussion("my-group/my-project", mr_iid=42, discussion_id="abc123", body="Fixed.")
client.resolve_discussion("my-group/my-project", mr_iid=42, discussion_id="abc123", resolved=True)
# Inline diff comment
from qodev_gitlab_api import DiffPosition
client.create_mr_discussion(
"my-group/my-project",
mr_iid=42,
body="Consider renaming this variable.",
position=DiffPosition(file_path="src/main.py", new_line=15),
)
Pipelines and Jobs
pipelines = client.get_pipelines("my-group/my-project", ref="main")
pipeline = client.get_pipeline("my-group/my-project", pipeline_id=1001)
jobs = client.get_pipeline_jobs("my-group/my-project", pipeline_id=1001)
# Job details, logs, and artifacts
job = client.get_job("my-group/my-project", job_id=5001)
log = client.get_job_log("my-group/my-project", job_id=5001)
artifact = client.get_job_artifact("my-group/my-project", job_id=5001, artifact_path="report.xml")
# Retry a failed job
client.retry_job("my-group/my-project", job_id=5001)
# Wait for pipeline completion (blocks until done or timeout)
result = client.wait_for_pipeline("my-group/my-project", pipeline_id=1001, timeout_seconds=600)
print(result["final_status"]) # "success", "failed", "canceled", "skipped", or "timeout"
Issues
issues = client.get_issues("my-group/my-project", state="opened", labels="bug")
issue = client.get_issue("my-group/my-project", issue_iid=10)
issue = client.create_issue(
"my-group/my-project",
title="Fix login bug",
description="Users cannot log in with SSO.",
labels="bug,urgent",
assignee_ids=[123],
)
client.update_issue("my-group/my-project", issue_iid=10, labels="bug,resolved")
client.close_issue("my-group/my-project", issue_iid=10)
# Comments
notes = client.get_issue_notes("my-group/my-project", issue_iid=10)
client.create_issue_note("my-group/my-project", issue_iid=10, body="Investigating this now.")
Releases
releases = client.get_releases("my-group/my-project")
release = client.get_release("my-group/my-project", tag_name="v1.0.0")
release = client.create_release(
"my-group/my-project",
tag_name="v1.1.0",
name="Version 1.1.0",
description="## What's new\n- Feature A\n- Bug fix B",
ref="main",
)
client.update_release("my-group/my-project", tag_name="v1.1.0", description="Updated notes.")
client.delete_release("my-group/my-project", tag_name="v1.1.0")
CI/CD Variables
variables = client.list_project_variables("my-group/my-project")
var = client.get_project_variable("my-group/my-project", key="API_KEY")
# Create or update (upsert)
var, action = client.set_project_variable(
"my-group/my-project",
key="API_KEY",
value="secret-value",
masked=True,
protected=True,
)
print(action) # "created" or "updated"
File Operations
# Read a file from the repository
content = client.get_file_content("my-group/my-project", file_path="README.md", ref="main")
# Upload a file (for embedding in issues/MRs)
from qodev_gitlab_api import FileFromPath
result = client.upload_file("my-group/my-project", source=FileFromPath(path="/tmp/screenshot.png"))
Error Handling
All API errors raise typed exceptions that inherit from GitLabError:
from qodev_gitlab_api import GitLabClient, AuthenticationError, NotFoundError, APIError, ConfigurationError
try:
client = GitLabClient()
mr = client.get_merge_request("my-group/my-project", mr_iid=999)
except ConfigurationError:
print("Missing or invalid GITLAB_TOKEN / GITLAB_URL")
except AuthenticationError:
print("Token is invalid or expired")
except NotFoundError:
print("Merge request not found")
except APIError as e:
print(f"API error {e.status_code}: {e.response_body}")
Requirements
- Python 3.11+
- httpx >= 0.28.1
- python-dotenv >= 1.1.0
License
MIT -- see LICENSE for details.
Metadata
Release files for qodev-gitlab-api 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qodev_gitlab_api-0.2.2.tar.gz | 14.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qodev_gitlab_api-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.3 kB
Release files / qodev_gitlab_api-0.2.2.tar.gz
| Download URL | qodev_gitlab_api-0.2.2.tar.gz |
|---|---|
| Size | 14.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c5e017c1888f95980f89c2cdec0f21f16bb2ec773c688667d7539646d51d3d5
|
|
BLAKE2b-256 checksum How to use checksums |
b69812f3516d52e1878d580b944a8a5e8f394f839075e0a322b8c6c630bc6f04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 20, 2026.
Transparency logRelease files / qodev_gitlab_api-0.2.2-py3-none-any.whl
| Download URL | qodev_gitlab_api-0.2.2-py3-none-any.whl |
|---|---|
| Size | 16.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c893573f2d27d90f7563076c5b421f025a8726f5fe37029b67c804e63e04bfea
|
|
BLAKE2b-256 checksum How to use checksums |
fbb5f0057a167270a441027da611b1725b9b41db0cfafadd95c187f55f45ad7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 20, 2026.
Transparency log