The python wrapper for the GitLab REST and GraphQL APIs.
—
Issue tracking, merge request workflows, code review processes, and collaborative development features. Covers comprehensive issue management, MR approvals, discussions, state transitions, and collaborative development workflows.
class ProjectIssue(
SubscribableMixin,
TodoMixin,
TimeTrackingMixin,
SaveMixin,
ObjectDeleteMixin,
RESTObject
):
"""
Project issue object with comprehensive workflow management.
Key Attributes:
- id: Issue ID
- iid: Internal issue ID (project-specific)
- title: Issue title
- description: Issue description/body
- state: Issue state ("opened", "closed")
- labels: List of label names
- milestone: Milestone information
- assignees: List of assigned users
- author: Issue author information
- created_at: Creation timestamp
- updated_at: Last update timestamp
- closed_at: Close timestamp
- due_date: Due date
- weight: Issue weight/points
- user_notes_count: Number of comments
- merge_requests_count: Number of related MRs
- upvotes: Number of upvotes
- downvotes: Number of downvotes
- confidential: Confidential issue flag
- web_url: Issue web URL
"""
def save(self) -> None:
"""Save issue changes."""
def delete(self) -> None:
"""Delete issue permanently."""
def subscribe(self) -> dict:
"""Subscribe to issue notifications."""
def unsubscribe(self) -> dict:
"""Unsubscribe from issue notifications."""
def todo(self) -> dict:
"""Create todo item for issue."""
def move(self, to_project_id: int) -> dict:
"""
Move issue to another project.
Parameters:
- to_project_id: Target project ID
Returns:
Dictionary with move result
"""
def clone(self, to_project_id: int, with_notes: bool = False) -> dict:
"""
Clone issue to another project.
Parameters:
- to_project_id: Target project ID
- with_notes: Include notes in clone
Returns:
Dictionary with clone result
"""
class ProjectIssueManager(CRUDMixin[ProjectIssue]):
"""Manager for project issues with comprehensive filtering."""
def list(
self,
state: str | None = None,
labels: str | list[str] | None = None,
milestone: str | None = None,
scope: str | None = None,
author_id: int | None = None,
author_username: str | None = None,
assignee_id: int | None = None,
assignee_username: str | None = None,
my_reaction_emoji: str | None = None,
order_by: str | None = None,
sort: str | None = None,
search: str | None = None,
created_after: str | None = None,
created_before: str | None = None,
updated_after: str | None = None,
updated_before: str | None = None,
confidential: bool | None = None,
not_labels: str | list[str] | None = None,
not_milestone: str | None = None,
not_author_id: int | None = None,
not_assignee_id: int | None = None,
not_my_reaction_emoji: str | None = None,
issue_type: str | None = None,
weight: int | None = None,
**kwargs
) -> list[ProjectIssue]:
"""List project issues with extensive filtering."""
def create(self, data: dict, **kwargs) -> ProjectIssue:
"""
Create new issue.
Required fields:
- title: Issue title
Optional fields:
- description: Issue description
- confidential: Make issue confidential
- assignee_ids: List of assignee user IDs
- milestone_id: Milestone ID
- labels: Comma-separated label names
- created_at: Creation date (admin only)
- due_date: Due date (ISO format)
- merge_request_to_resolve_discussions_of: MR ID to resolve discussions
- discussion_to_resolve: Discussion ID to resolve
- weight: Issue weight
- epic_id: Epic ID (GitLab Premium)
- issue_type: Issue type ("issue", "incident", "test_case", "requirement", "task")
"""class ProjectMergeRequest(
SubscribableMixin,
TodoMixin,
TimeTrackingMixin,
SaveMixin,
ObjectDeleteMixin,
RESTObject
):
"""
Project merge request object with comprehensive workflow management.
Key Attributes:
- id: MR ID
- iid: Internal MR ID (project-specific)
- title: MR title
- description: MR description/body
- state: MR state ("opened", "closed", "merged")
- source_branch: Source branch name
- target_branch: Target branch name
- source_project_id: Source project ID
- target_project_id: Target project ID
- labels: List of label names
- milestone: Milestone information
- assignees: List of assigned users
- reviewers: List of reviewer users
- author: MR author information
- merge_user: User who merged the MR
- merged_at: Merge timestamp
- created_at: Creation timestamp
- updated_at: Last update timestamp
- closed_at: Close timestamp
- merge_commit_sha: Merge commit SHA
- squash_commit_sha: Squash commit SHA
- user_notes_count: Number of comments
- upvotes: Number of upvotes
- downvotes: Number of downvotes
- work_in_progress: WIP status
- draft: Draft status
- merge_when_pipeline_succeeds: Auto-merge setting
- merge_status: Merge status
- sha: Source branch HEAD SHA
- merge_commit_sha: Merge commit SHA
- squash: Squash commits on merge
- web_url: MR web URL
- changes_count: Number of changed files
- conflicts: Merge conflicts information
- diverged_commits_count: Diverged commits count
- rebase_in_progress: Rebase status
- approvals_before_merge: Required approvals
- reference: MR reference string
- references: Related references
- task_completion_status: Task completion info
- has_conflicts: Conflicts flag
- blocking_discussions_resolved: Discussions resolved flag
"""
def save(self) -> None:
"""Save MR changes."""
def delete(self) -> None:
"""Delete MR permanently."""
def subscribe(self) -> dict:
"""Subscribe to MR notifications."""
def unsubscribe(self) -> dict:
"""Unsubscribe from MR notifications."""
def merge(
self,
merge_commit_message: str | None = None,
squash_commit_message: str | None = None,
squash: bool | None = None,
should_remove_source_branch: bool | None = None,
merge_when_pipeline_succeeds: bool | None = None,
sha: str | None = None,
**kwargs
) -> dict:
"""
Merge the merge request.
Parameters:
- merge_commit_message: Custom merge commit message
- squash_commit_message: Custom squash commit message
- squash: Squash commits on merge
- should_remove_source_branch: Remove source branch after merge
- merge_when_pipeline_succeeds: Merge when pipeline succeeds
- sha: Source branch HEAD SHA for merge validation
Returns:
Dictionary with merge result
Raises:
GitlabMRForbiddenError: If merge is not allowed
"""
def cancel_merge_when_pipeline_succeeds(self) -> dict:
"""Cancel auto-merge when pipeline succeeds."""
def rebase(self, skip_ci: bool = False) -> dict:
"""
Rebase merge request.
Parameters:
- skip_ci: Skip CI pipeline for rebase commit
Returns:
Dictionary with rebase result
Raises:
GitlabMRRebaseError: If rebase fails
"""
def approve(self, sha: str | None = None) -> dict:
"""
Approve merge request.
Parameters:
- sha: Source branch HEAD SHA
Returns:
Dictionary with approval result
Raises:
GitlabMRApprovalError: If approval fails
"""
def unapprove(self) -> dict:
"""
Remove approval from merge request.
Returns:
Dictionary with unapproval result
Raises:
GitlabMRApprovalError: If unapproval fails
"""
def reset_approvals(self) -> dict:
"""
Reset all approvals for merge request.
Returns:
Dictionary with reset result
Raises:
GitlabMRResetApprovalError: If reset fails
"""
class ProjectMergeRequestManager(CRUDMixin[ProjectMergeRequest]):
"""Manager for project merge requests with comprehensive filtering."""
def list(
self,
state: str | None = None,
order_by: str | None = None,
sort: str | None = None,
milestone: str | None = None,
view: str | None = None,
labels: str | list[str] | None = None,
with_labels_details: bool | None = None,
with_merge_status_recheck: bool | None = None,
created_after: str | None = None,
created_before: str | None = None,
updated_after: str | None = None,
updated_before: str | None = None,
scope: str | None = None,
author_id: int | None = None,
author_username: str | None = None,
assignee_id: int | None = None,
assignee_username: str | None = None,
reviewer_id: int | None = None,
reviewer_username: str | None = None,
my_reaction_emoji: str | None = None,
source_branch: str | None = None,
target_branch: str | None = None,
search: str | None = None,
wip: str | None = None,
not_labels: str | list[str] | None = None,
not_milestone: str | None = None,
not_author_id: int | None = None,
not_assignee_id: int | None = None,
not_reviewer_id: int | None = None,
not_my_reaction_emoji: str | None = None,
deployed_before: str | None = None,
deployed_after: str | None = None,
environment: str | None = None,
**kwargs
) -> list[ProjectMergeRequest]:
"""List project merge requests with extensive filtering."""
def create(self, data: dict, **kwargs) -> ProjectMergeRequest:
"""
Create new merge request.
Required fields:
- source_branch: Source branch name
- target_branch: Target branch name
- title: MR title
Optional fields:
- description: MR description
- assignee_ids: List of assignee user IDs
- reviewer_ids: List of reviewer user IDs
- milestone_id: Milestone ID
- labels: Comma-separated label names
- target_project_id: Target project ID (for cross-project MRs)
- remove_source_branch: Remove source branch when merged
- squash: Squash commits when merging
- allow_collaboration: Allow commits from maintainers
- allow_maintainer_to_push: Allow maintainer to push (deprecated)
"""# Both issues and merge requests have similar nested resource managers:
# Notes/Comments
@property
def notes(self) -> ProjectIssueNoteManager | ProjectMergeRequestNoteManager:
"""Access issue/MR comments and discussions."""
# Award Emojis
@property
def award_emojis(self) -> ProjectIssueAwardEmojiManager | ProjectMergeRequestAwardEmojiManager:
"""Access emoji reactions."""
# Resource Links
@property
def links(self) -> ProjectIssueLinkManager:
"""Access issue links (issues only)."""
# Time Tracking
def time_stats(self) -> dict:
"""Get time tracking statistics."""
def add_spent_time(self, duration: str) -> dict:
"""
Add spent time.
Parameters:
- duration: Time duration (e.g., "1h 30m", "2d")
"""
def reset_spent_time(self) -> dict:
"""Reset all spent time."""
def reset_time_estimate(self) -> dict:
"""Reset time estimate."""class ProjectMergeRequestApproval(RESTObject):
"""
MR approval information.
Attributes:
- approvals_required: Required approvals count
- approvals_left: Remaining approvals needed
- approved_by: List of users who approved
- suggested_approvers: Suggested approver users
- approvers: Configured approver users
- approver_groups: Configured approver groups
"""
class ProjectMergeRequestApprovalManager(GetWithoutIdMixin[ProjectMergeRequestApproval], UpdateMixin[ProjectMergeRequestApproval]):
"""Manager for MR approval settings."""
def get(self, **kwargs) -> ProjectMergeRequestApproval:
"""Get MR approval information."""
def update(self, data: dict, **kwargs) -> ProjectMergeRequestApproval:
"""Update MR approval settings."""
class ProjectApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject):
"""
Project approval rule configuration.
Attributes:
- id: Rule ID
- name: Rule name
- rule_type: Rule type
- approvals_required: Required approvals
- users: Approver users
- groups: Approver groups
- protected_branches: Protected branches
- contains_hidden_groups: Hidden groups flag
"""
class ProjectApprovalRuleManager(CRUDMixin[ProjectApprovalRule]):
"""Manager for project approval rules."""class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject):
"""
Issue comment/note object.
Attributes:
- id: Note ID
- body: Comment body/content
- author: Note author information
- created_at: Creation timestamp
- updated_at: Update timestamp
- system: System note flag
- noteable_id: Parent issue/MR ID
- noteable_type: Parent type
- resolvable: Resolvable discussion flag
- resolved: Resolution status
- resolved_by: User who resolved
"""
def save(self) -> None:
"""Save note changes."""
def delete(self) -> None:
"""Delete note."""
class ProjectIssueNoteManager(CRUDMixin[ProjectIssueNote]):
"""Manager for issue notes/comments."""
def create(self, data: dict, **kwargs) -> ProjectIssueNote:
"""
Create new note.
Required fields:
- body: Comment content
Optional fields:
- confidential: Make note confidential
- created_at: Creation date (admin only)
"""
# Similar classes exist for MR notes
class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject):
"""Merge request comment/note object."""
class ProjectMergeRequestNoteManager(CRUDMixin[ProjectMergeRequestNote]):
"""Manager for merge request notes/comments."""import gitlab
gl = gitlab.Gitlab("https://gitlab.example.com", private_token="your-token")
project = gl.projects.get(123)
# Issue management
issues = project.issues.list(
state="opened",
labels=["bug", "high-priority"],
assignee_id=456
)
# Create issue
issue_data = {
"title": "Critical Bug Fix",
"description": "Detailed bug description...",
"labels": "bug,critical",
"assignee_ids": [456],
"milestone_id": 789,
"due_date": "2024-12-31"
}
issue = project.issues.create(issue_data)
# Update issue
issue.title = "Updated Title"
issue.description = "Updated description"
issue.save()
# Issue operations
issue.subscribe()
issue.todo()
issue.move(to_project_id=999)
# Add time tracking
issue.add_spent_time("2h 30m")
time_stats = issue.time_stats()
# Issue comments
notes = issue.notes.list()
comment_data = {"body": "This is a comment"}
comment = issue.notes.create(comment_data)
# Merge request management
mrs = project.mergerequests.list(
state="opened",
target_branch="main",
author_id=456
)
# Create merge request
mr_data = {
"source_branch": "feature-branch",
"target_branch": "main",
"title": "Add new feature",
"description": "Feature implementation details...",
"assignee_ids": [456],
"reviewer_ids": [789],
"remove_source_branch": True,
"squash": True
}
mr = project.mergerequests.create(mr_data)
# MR operations
mr.approve()
mr.merge(
merge_commit_message="Merge feature branch",
should_remove_source_branch=True,
squash=True
)
# MR approval management
approval = mr.approvals.get()
print(f"Approvals needed: {approval.approvals_left}")
# Approval rules
rules = project.approvalrules.list()
rule_data = {
"name": "Security Review",
"approvals_required": 2,
"user_ids": [123, 456],
"protected_branch_ids": [1, 2]
}
rule = project.approvalrules.create(rule_data)
# Award emojis
thumbs_up = issue.award_emojis.create({"name": "thumbsup"})
reactions = issue.award_emojis.list()class GitlabMRForbiddenError(GitlabOperationError):
"""Raised when MR operation is forbidden."""
pass
class GitlabMRApprovalError(GitlabOperationError):
"""Raised when MR approval operation fails."""
pass
class GitlabMRRebaseError(GitlabOperationError):
"""Raised when MR rebase fails."""
pass
class GitlabMRResetApprovalError(GitlabOperationError):
"""Raised when MR approval reset fails."""
pass
class GitlabMRClosedError(GitlabOperationError):
"""Raised when operating on closed MR."""
passExample error handling:
try:
mr.merge()
except gitlab.GitlabMRForbiddenError as e:
print(f"Merge not allowed: {e}")
except gitlab.GitlabMRClosedError:
print("Cannot merge closed MR")
try:
mr.approve()
except gitlab.GitlabMRApprovalError as e:
print(f"Approval failed: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-python-gitlab