Skip to content

GitHub Communication Service

A shared service for GitHub API communication using PyGithub. Provides typed interfaces for working with issues, milestones, and connection status.

Table of Contents


Quick Start

from src.shared_services.cloud_com.github_com.api import (
    initialize_github,
    RepositoryService,
    IssueState,
)

# Initialize (usually done automatically after OAuth login)
initialize_github()

# Create a service for a specific repository
service = RepositoryService("GehaAnlagenbauGmbH", "GehaSoftwareHubRI")

# Get open issues
issues = service.get_issues(state=IssueState.OPEN)
for issue in issues:
    print(f"#{issue.number}: {issue.title}")

Initialization

The GitHub client must be initialized before use. This is typically done automatically after OAuth login in login_manager.py.

from src.shared_services.cloud_com.github_com.api import initialize_github

# Uses token from OAuth system automatically
initialize_github()

Manual Initialization

from src.shared_services.cloud_com.github_com.api import initialize_github

# Provide explicit token
initialize_github(token="ghp_your_token_here")

Check Initialization Status

from src.shared_services.cloud_com.github_com.api import is_github_initialized

if not is_github_initialized():
    initialize_github()

Reinitialize After Token Refresh

from src.shared_services.cloud_com.github_com.api import get_github_client

client = get_github_client()
client.reinitialize()  # Fetches fresh token from OAuth

Connection Status

Test Connection

from src.shared_services.cloud_com.github_com.api import test_github_connection

if test_github_connection():
    print("Connected to GitHub API")
else:
    print("Cannot reach GitHub API")

Quick Online Check

from src.shared_services.cloud_com.github_com.api import is_github_online

# Based on last request status (no new API call)
if is_github_online():
    # Safe to make API calls
    pass

Detailed Connection Status

from src.shared_services.cloud_com.github_com.api import get_connection_status

status = get_connection_status()

print(f"Online: {status.is_online}")
print(f"Last check: {status.last_check}")
print(f"Rate limit remaining: {status.rate_limit_remaining}")
print(f"Rate limit resets at: {status.rate_limit_reset}")

if not status.is_online:
    print(f"Error: {status.error_message}")

Working with Issues

Create a Repository Service

from src.shared_services.cloud_com.github_com.api import RepositoryService

service = RepositoryService("owner", "repo-name")

Get Issues

from src.shared_services.cloud_com.github_com.api import (
    RepositoryService,
    IssueState,
)

service = RepositoryService("GehaAnlagenbauGmbH", "GehaSoftwareHubRI")

# Get all open issues
open_issues = service.get_issues(state=IssueState.OPEN)

# Get all closed issues
closed_issues = service.get_issues(state=IssueState.CLOSED)

# Get all issues (open and closed)
all_issues = service.get_issues(state=IssueState.ALL)

# Filter by labels
bugs = service.get_issues(state=IssueState.OPEN, labels=["bug"])

# Filter by assignee
my_issues = service.get_issues(assignee="username")

# Filter by milestone
sprint_issues = service.get_issues(milestone="Sprint 1")

# Combine filters
critical_bugs = service.get_issues(
    state=IssueState.OPEN,
    labels=["bug", "priority-high"],
    assignee="username",
)

Get Single Issue

issue = service.get_issue(issue_number=42)

print(f"Title: {issue.title}")
print(f"State: {issue.state}")
print(f"Author: {issue.user.login}")
print(f"Body: {issue.body}")
print(f"URL: {issue.html_url}")
print(f"Comments: {issue.comments_count}")

# Labels
for label in issue.labels:
    print(f"  Label: {label.name} (#{label.color})")

# Assignee (may be None)
if issue.assignee:
    print(f"Assigned to: {issue.assignee.login}")

# Milestone (may be None)
if issue.milestone:
    print(f"Milestone: {issue.milestone.title}")

Get Issue Comments

comments = service.get_issue_comments(issue_number=42)

for comment in comments:
    print(f"{comment.user.login} at {comment.created_at}:")
    print(f"  {comment.body}")
    print(f"  URL: {comment.html_url}")

Create Issue

new_issue = service.create_issue(
    title="Bug: Application crashes on startup",
    body="## Steps to reproduce\n1. Open app\n2. See crash",
    labels=["bug", "priority-high"],
    assignees=["username"],
    milestone=1,  # Milestone number
)

print(f"Created issue #{new_issue.number}")
print(f"URL: {new_issue.html_url}")

Update Issue Labels

# Replace all labels on an issue
updated_issue = service.update_issue_labels(
    issue_number=42,
    labels=["bug", "confirmed", "in-progress"],
)

Working with Milestones

Get Milestones

from src.shared_services.cloud_com.github_com.api import (
    RepositoryService,
    MilestoneState,
)

service = RepositoryService("GehaAnlagenbauGmbH", "GehaSoftwareHubRI")

# Get open milestones
open_milestones = service.get_milestones(state=MilestoneState.OPEN)

# Get closed milestones
closed_milestones = service.get_milestones(state=MilestoneState.CLOSED)

# Get all milestones
all_milestones = service.get_milestones(state=MilestoneState.ALL)

# Display milestone progress
for ms in open_milestones:
    total = ms.open_issues + ms.closed_issues
    if total > 0:
        progress = ms.closed_issues / total * 100
        print(f"{ms.title}: {progress:.0f}% complete")
    else:
        print(f"{ms.title}: No issues")

    if ms.due_on:
        print(f"  Due: {ms.due_on.strftime('%Y-%m-%d')}")

Exception Handling

All exceptions inherit from GitHubError, allowing you to catch all GitHub-related errors with a single except clause.

Exception Hierarchy

Exception Description
GitHubError Base exception for all GitHub operations
GitHubAuthenticationError Invalid or missing token
GitHubConnectionError Network or API failures
GitHubRateLimitError API rate limit exceeded
GitHubNotFoundError Resource not found (404)
GitHubPermissionError Access forbidden (403)
GitHubClientNotInitializedError Client used before initialization

Basic Error Handling

from src.shared_services.cloud_com.github_com.api import (
    RepositoryService,
    GitHubError,
)

service = RepositoryService("owner", "repo")

try:
    issues = service.get_issues()
except GitHubError as e:
    print(f"GitHub operation failed: {e}")

Specific Error Handling

from src.shared_services.cloud_com.github_com.api import (
    RepositoryService,
    GitHubNotFoundError,
    GitHubPermissionError,
    GitHubRateLimitError,
    GitHubConnectionError,
    GitHubAuthenticationError,
)

service = RepositoryService("owner", "repo")

try:
    issue = service.get_issue(99999)
except GitHubNotFoundError:
    print("Issue does not exist")
except GitHubPermissionError:
    print("No permission to access this resource")
except GitHubRateLimitError as e:
    print(f"Rate limited. Retry after: {e.reset_time}")
except GitHubAuthenticationError:
    print("Invalid token - please re-authenticate")
except GitHubConnectionError:
    print("Network error - check internet connection")

Handling Uninitialized Client

from src.shared_services.cloud_com.github_com.api import (
    get_github_client,
    initialize_github,
    GitHubClientNotInitializedError,
)

try:
    client = get_github_client()
except GitHubClientNotInitializedError:
    initialize_github()
    client = get_github_client()

Type Reference

Enums

IssueState

from src.shared_services.cloud_com.github_com.api import IssueState

IssueState.OPEN    # Open issues only
IssueState.CLOSED  # Closed issues only
IssueState.ALL     # All issues

MilestoneState

from src.shared_services.cloud_com.github_com.api import MilestoneState

MilestoneState.OPEN    # Open milestones only
MilestoneState.CLOSED  # Closed milestones only
MilestoneState.ALL     # All milestones

Dataclasses

GitHubUser

Field Type Description
login str Username
id int Unique user ID
avatar_url str URL to avatar image

GitHubLabel

Field Type Description
name str Label name
color str Hex color (without #)
description str Label description

GitHubMilestone

Field Type Description
number int Milestone number
title str Milestone title
description str Milestone description
state str "open" or "closed"
due_on datetime | None Due date
open_issues int Count of open issues
closed_issues int Count of closed issues
created_at datetime Creation timestamp
updated_at datetime Last update timestamp
html_url str Web URL

GitHubComment

Field Type Description
id int Comment ID
body str Comment text (Markdown)
user GitHubUser Comment author
created_at datetime Creation timestamp
updated_at datetime Last edit timestamp
html_url str Web URL

GitHubIssue

Field Type Description
number int Issue number
title str Issue title
body str Issue description (Markdown)
state str "open" or "closed"
user GitHubUser Issue creator
labels List[GitHubLabel] Attached labels
assignee GitHubUser | None Assigned user
milestone GitHubMilestone | None Associated milestone
created_at datetime | None Creation timestamp
updated_at datetime | None Last update timestamp
closed_at datetime | None Close timestamp
html_url str Web URL
comments_count int Number of comments

ConnectionStatus

Field Type Description
is_online bool API reachable
last_check datetime | None Last status update
rate_limit_remaining int | None API calls remaining
rate_limit_reset datetime | None When limit resets
error_message str | None Last error (if offline)

Full Import Reference

from src.shared_services.cloud_com.github_com.api import (
    # Initialization
    initialize_github,
    get_github_client,
    test_github_connection,
    get_connection_status,
    is_github_online,
    is_github_initialized,

    # Services
    RepositoryService,

    # Enums
    IssueState,
    MilestoneState,

    # Dataclasses
    ConnectionStatus,
    GitHubComment,
    GitHubIssue,
    GitHubLabel,
    GitHubMilestone,
    GitHubUser,

    # Exceptions
    GitHubError,
    GitHubAuthenticationError,
    GitHubClientNotInitializedError,
    GitHubConnectionError,
    GitHubNotFoundError,
    GitHubPermissionError,
    GitHubRateLimitError,
)

API Reference

src.shared_services.cloud_com.github_com.api

Public API for GitHub communication service.

USAGE

from src.shared_services.cloud_com.github_com.api import ( initialize_github, test_github_connection, get_connection_status, is_github_online, RepositoryService, IssueState, MilestoneState, )

Initialize at app startup (after OAuth login)

initialize_github()

Check connection status

if is_github_online(): status = get_connection_status() print(f"Rate limit remaining: {status.rate_limit_remaining}")

Work with repositories

service = RepositoryService("owner", "repo")

Get issues

issues = service.get_issues(state=IssueState.OPEN) for issue in issues: print(f"#{issue.number}: {issue.title}")

Get milestones

milestones = service.get_milestones()

Get releases

releases = service.get_releases() latest = service.get_latest_release()

ConnectionStatus dataclass

GitHub API connection status.

Mutable dataclass for tracking connection state.

Attributes:

Name Type Description
is_online bool

Whether API is reachable.

last_check Optional[datetime]

When status was last updated.

rate_limit_remaining Optional[int]

API calls remaining in current window.

rate_limit_reset Optional[datetime]

When rate limit resets (UTC).

error_message Optional[str]

Last error message (if offline).

Example

status = get_connection_status() if status.is_online: print(f"Online, {status.rate_limit_remaining} requests remaining") else: print(f"Offline: {status.error_message}")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(slots=True)
class ConnectionStatus:
    """
    GitHub API connection status.

    Mutable dataclass for tracking connection state.

    Attributes:
        is_online: Whether API is reachable.
        last_check: When status was last updated.
        rate_limit_remaining: API calls remaining in current window.
        rate_limit_reset: When rate limit resets (UTC).
        error_message: Last error message (if offline).

    Example:
        status = get_connection_status()
        if status.is_online:
            print(f"Online, {status.rate_limit_remaining} requests remaining")
        else:
            print(f"Offline: {status.error_message}")
    """

    is_online: bool = False
    last_check: Optional[datetime] = None
    rate_limit_remaining: Optional[int] = None
    rate_limit_reset: Optional[datetime] = None
    error_message: Optional[str] = None

GitHubAuthenticationError

Bases: GitHubError

Raised when authentication fails.

This occurs when: - Token is missing or invalid - Token has been revoked - Token lacks required scopes

Example

try: client.initialize(token="invalid_token") except GitHubAuthenticationError: print("Please re-authenticate with GitHub")

Source code in src\shared_services\cloud_com\github_com\exceptions.py
class GitHubAuthenticationError(GitHubError):
    """
    Raised when authentication fails.

    This occurs when:
    - Token is missing or invalid
    - Token has been revoked
    - Token lacks required scopes

    Example:
        try:
            client.initialize(token="invalid_token")
        except GitHubAuthenticationError:
            print("Please re-authenticate with GitHub")
    """

    pass

GitHubClientNotInitializedError

Bases: GitHubError

Raised when client is used before initialization.

Call initialize_github() before using the client.

Example

from src.shared_services.cloud_com.github_com.api import ( initialize_github, get_github_client, )

This would raise GitHubClientNotInitializedError
client = get_github_client()
Correct usage

initialize_github() client = get_github_client()

Source code in src\shared_services\cloud_com\github_com\exceptions.py
class GitHubClientNotInitializedError(GitHubError):
    """
    Raised when client is used before initialization.

    Call initialize_github() before using the client.

    Example:
        from src.shared_services.cloud_com.github_com.api import (
            initialize_github,
            get_github_client,
        )

        # This would raise GitHubClientNotInitializedError
        # client = get_github_client()

        # Correct usage
        initialize_github()
        client = get_github_client()
    """

    pass

GitHubComment dataclass

Issue or PR comment.

Attributes:

Name Type Description
id int

Unique comment ID.

body str

Comment text (Markdown).

user GitHubUser

Comment author.

created_at datetime

Creation timestamp.

updated_at datetime

Last edit timestamp.

html_url str

Web URL to comment.

Example

comments = service.get_issue_comments(issue_number=42) for comment in comments: print(f"{comment.user.login}: {comment.body[:50]}...")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(frozen=True, slots=True)
class GitHubComment:
    """
    Issue or PR comment.

    Attributes:
        id: Unique comment ID.
        body: Comment text (Markdown).
        user: Comment author.
        created_at: Creation timestamp.
        updated_at: Last edit timestamp.
        html_url: Web URL to comment.

    Example:
        comments = service.get_issue_comments(issue_number=42)
        for comment in comments:
            print(f"{comment.user.login}: {comment.body[:50]}...")
    """

    id: int
    body: str
    user: GitHubUser
    created_at: datetime
    updated_at: datetime
    html_url: str

GitHubConnectionError

Bases: GitHubError

Raised when network connection fails.

This occurs when: - No internet connection - GitHub API is unreachable - Request times out

Example

try: client.test_connection() except GitHubConnectionError: print("Cannot reach GitHub API")

Source code in src\shared_services\cloud_com\github_com\exceptions.py
class GitHubConnectionError(GitHubError):
    """
    Raised when network connection fails.

    This occurs when:
    - No internet connection
    - GitHub API is unreachable
    - Request times out

    Example:
        try:
            client.test_connection()
        except GitHubConnectionError:
            print("Cannot reach GitHub API")
    """

    pass

GitHubError

Bases: Exception

Base exception for GitHub operations.

All GitHub-related exceptions inherit from this class, allowing callers to catch all GitHub errors with a single except clause.

Example

try: service.get_issues() except GitHubError as e: print(f"GitHub operation failed: {e}")

Source code in src\shared_services\cloud_com\github_com\exceptions.py
class GitHubError(Exception):
    """
    Base exception for GitHub operations.

    All GitHub-related exceptions inherit from this class,
    allowing callers to catch all GitHub errors with a single except clause.

    Example:
        try:
            service.get_issues()
        except GitHubError as e:
            print(f"GitHub operation failed: {e}")
    """

    pass

GitHubIssue dataclass

Repository issue.

Note: Pull requests are also issues in GitHub's API, but this service filters them out. Only true issues are returned.

Attributes:

Name Type Description
number int

Issue number (unique within repo).

title str

Issue title.

body str

Issue description (Markdown).

state str

Current state (open/closed).

user GitHubUser

Issue creator.

labels List[GitHubLabel]

Attached labels.

assignee Optional[GitHubUser]

Assigned user (if any).

milestone Optional[GitHubMilestone]

Associated milestone (if any).

created_at Optional[datetime]

Creation timestamp.

updated_at Optional[datetime]

Last update timestamp.

closed_at Optional[datetime]

Close timestamp (if closed).

html_url str

Web URL to issue.

comments_count int

Number of comments.

Example

issues = service.get_issues(state=IssueState.OPEN) for issue in issues: print(f"#{issue.number}: {issue.title}") if issue.assignee: print(f" Assigned to: {issue.assignee.login}")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(frozen=True, slots=True)
class GitHubIssue:
    """
    Repository issue.

    Note: Pull requests are also issues in GitHub's API, but this service
    filters them out. Only true issues are returned.

    Attributes:
        number: Issue number (unique within repo).
        title: Issue title.
        body: Issue description (Markdown).
        state: Current state (open/closed).
        user: Issue creator.
        labels: Attached labels.
        assignee: Assigned user (if any).
        milestone: Associated milestone (if any).
        created_at: Creation timestamp.
        updated_at: Last update timestamp.
        closed_at: Close timestamp (if closed).
        html_url: Web URL to issue.
        comments_count: Number of comments.

    Example:
        issues = service.get_issues(state=IssueState.OPEN)
        for issue in issues:
            print(f"#{issue.number}: {issue.title}")
            if issue.assignee:
                print(f"  Assigned to: {issue.assignee.login}")
    """

    number: int
    title: str
    body: str
    state: str
    user: GitHubUser
    labels: List[GitHubLabel] = field(default_factory=list)
    assignee: Optional[GitHubUser] = None
    milestone: Optional[GitHubMilestone] = None
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None
    closed_at: Optional[datetime] = None
    html_url: str = ""
    comments_count: int = 0

GitHubLabel dataclass

Issue or PR label.

Attributes:

Name Type Description
name str

Label name (e.g., "bug", "enhancement").

color str

Hex color code without # prefix.

description str

Label description.

Example

for label in issue.labels: print(f"Label: {label.name} (#{label.color})")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(frozen=True, slots=True)
class GitHubLabel:
    """
    Issue or PR label.

    Attributes:
        name: Label name (e.g., "bug", "enhancement").
        color: Hex color code without # prefix.
        description: Label description.

    Example:
        for label in issue.labels:
            print(f"Label: {label.name} (#{label.color})")
    """

    name: str
    color: str
    description: str = ""

GitHubMilestone dataclass

Repository milestone.

Attributes:

Name Type Description
number int

Milestone number (unique within repo).

title str

Milestone title.

description str

Milestone description.

state str

Current state (open/closed).

due_on Optional[datetime]

Due date (if set).

open_issues int

Count of open issues.

closed_issues int

Count of closed issues.

created_at datetime

Creation timestamp.

updated_at datetime

Last update timestamp.

html_url str

Web URL to milestone.

Example

milestone = issue.milestone if milestone: progress = milestone.closed_issues / ( milestone.open_issues + milestone.closed_issues ) print(f"Milestone: {milestone.title} ({progress:.0%} complete)")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(frozen=True, slots=True)
class GitHubMilestone:
    """
    Repository milestone.

    Attributes:
        number: Milestone number (unique within repo).
        title: Milestone title.
        description: Milestone description.
        state: Current state (open/closed).
        due_on: Due date (if set).
        open_issues: Count of open issues.
        closed_issues: Count of closed issues.
        created_at: Creation timestamp.
        updated_at: Last update timestamp.
        html_url: Web URL to milestone.

    Example:
        milestone = issue.milestone
        if milestone:
            progress = milestone.closed_issues / (
                milestone.open_issues + milestone.closed_issues
            )
            print(f"Milestone: {milestone.title} ({progress:.0%} complete)")
    """

    number: int
    title: str
    description: str
    state: str
    due_on: Optional[datetime]
    open_issues: int
    closed_issues: int
    created_at: datetime
    updated_at: datetime
    html_url: str

GitHubNotFoundError

Bases: GitHubError

Raised when a resource is not found (404).

This occurs when: - Repository does not exist - Issue number is invalid - User does not exist

Example

try: service.get_issue(99999) except GitHubNotFoundError: print("Issue not found")

Source code in src\shared_services\cloud_com\github_com\exceptions.py
class GitHubNotFoundError(GitHubError):
    """
    Raised when a resource is not found (404).

    This occurs when:
    - Repository does not exist
    - Issue number is invalid
    - User does not exist

    Example:
        try:
            service.get_issue(99999)
        except GitHubNotFoundError:
            print("Issue not found")
    """

    pass

GitHubPermissionError

Bases: GitHubError

Raised when access is forbidden (403).

This occurs when: - Token lacks required permissions - Repository is private and token has no access - Organization blocks access

Example

try: service.create_issue(title="Bug", body="Description") except GitHubPermissionError: print("No permission to create issues")

Source code in src\shared_services\cloud_com\github_com\exceptions.py
class GitHubPermissionError(GitHubError):
    """
    Raised when access is forbidden (403).

    This occurs when:
    - Token lacks required permissions
    - Repository is private and token has no access
    - Organization blocks access

    Example:
        try:
            service.create_issue(title="Bug", body="Description")
        except GitHubPermissionError:
            print("No permission to create issues")
    """

    pass

GitHubRateLimitError

Bases: GitHubError

Raised when API rate limit is exceeded.

Attributes:

Name Type Description
reset_time

When the rate limit resets (UTC datetime).

Example

try: service.get_issues() except GitHubRateLimitError as e: print(f"Rate limited. Retry after {e.reset_time}")

Source code in src\shared_services\cloud_com\github_com\exceptions.py
class GitHubRateLimitError(GitHubError):
    """
    Raised when API rate limit is exceeded.

    Attributes:
        reset_time: When the rate limit resets (UTC datetime).

    Example:
        try:
            service.get_issues()
        except GitHubRateLimitError as e:
            print(f"Rate limited. Retry after {e.reset_time}")
    """

    def __init__(
        self,
        message: str = "GitHub API rate limit exceeded",
        reset_time: Optional[datetime] = None,
    ) -> None:
        """
        Initialize rate limit error.

        Args:
            message: Error message.
            reset_time: When the rate limit resets.
        """
        super().__init__(message)
        self.reset_time = reset_time
__init__(message='GitHub API rate limit exceeded', reset_time=None)

Initialize rate limit error.

Parameters:

Name Type Description Default
message str

Error message.

'GitHub API rate limit exceeded'
reset_time Optional[datetime]

When the rate limit resets.

None
Source code in src\shared_services\cloud_com\github_com\exceptions.py
def __init__(
    self,
    message: str = "GitHub API rate limit exceeded",
    reset_time: Optional[datetime] = None,
) -> None:
    """
    Initialize rate limit error.

    Args:
        message: Error message.
        reset_time: When the rate limit resets.
    """
    super().__init__(message)
    self.reset_time = reset_time

GitHubRelease dataclass

Repository release.

Attributes:

Name Type Description
id int

Unique release ID.

tag_name str

Git tag for this release (e.g. "v1.0.0").

name str

Release title.

body str

Release notes (Markdown).

draft bool

Whether this is a draft release.

prerelease bool

Whether this is a prerelease.

author GitHubUser

Release creator.

created_at Optional[datetime]

Creation timestamp.

published_at Optional[datetime]

Publication timestamp.

html_url str

Web URL to release page.

assets List[GitHubReleaseAsset]

Downloadable release assets.

Example

releases = service.get_releases() for release in releases: print(f"{release.tag_name}: {release.name}")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(frozen=True, slots=True)
class GitHubRelease:
    """
    Repository release.

    Attributes:
        id: Unique release ID.
        tag_name: Git tag for this release (e.g. "v1.0.0").
        name: Release title.
        body: Release notes (Markdown).
        draft: Whether this is a draft release.
        prerelease: Whether this is a prerelease.
        author: Release creator.
        created_at: Creation timestamp.
        published_at: Publication timestamp.
        html_url: Web URL to release page.
        assets: Downloadable release assets.

    Example:
        releases = service.get_releases()
        for release in releases:
            print(f"{release.tag_name}: {release.name}")
    """

    id: int
    tag_name: str
    name: str
    body: str
    draft: bool
    prerelease: bool
    author: GitHubUser
    created_at: Optional[datetime]
    published_at: Optional[datetime]
    html_url: str
    assets: List[GitHubReleaseAsset] = field(default_factory=list)

GitHubReleaseAsset dataclass

Release asset (downloadable file).

Attributes:

Name Type Description
name str

Asset file name.

download_url str

Direct download URL.

size int

File size in bytes.

content_type str

MIME type of the asset.

download_count int

Number of downloads.

Example

for asset in release.assets: print(f"{asset.name} ({asset.size} bytes)")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(frozen=True, slots=True)
class GitHubReleaseAsset:
    """
    Release asset (downloadable file).

    Attributes:
        name: Asset file name.
        download_url: Direct download URL.
        size: File size in bytes.
        content_type: MIME type of the asset.
        download_count: Number of downloads.

    Example:
        for asset in release.assets:
            print(f"{asset.name} ({asset.size} bytes)")
    """

    name: str
    download_url: str
    size: int
    content_type: str
    download_count: int

GitHubUser dataclass

GitHub user information.

Attributes:

Name Type Description
login str

Username (handle).

id int

Unique user ID.

avatar_url str

URL to user avatar image.

Example

user = issue.user print(f"Created by: {user.login}")

Source code in src\shared_services\cloud_com\github_com\types.py
@dataclass(frozen=True, slots=True)
class GitHubUser:
    """
    GitHub user information.

    Attributes:
        login: Username (handle).
        id: Unique user ID.
        avatar_url: URL to user avatar image.

    Example:
        user = issue.user
        print(f"Created by: {user.login}")
    """

    login: str
    id: int
    avatar_url: str

IssueState

Bases: Enum

Issue state filter.

Example

issues = service.get_issues(state=IssueState.OPEN)

Source code in src\shared_services\cloud_com\github_com\types.py
class IssueState(Enum):
    """
    Issue state filter.

    Example:
        issues = service.get_issues(state=IssueState.OPEN)
    """

    OPEN = "open"
    CLOSED = "closed"
    ALL = "all"

MilestoneState

Bases: Enum

Milestone state filter.

Example

milestones = service.get_milestones(state=MilestoneState.OPEN)

Source code in src\shared_services\cloud_com\github_com\types.py
class MilestoneState(Enum):
    """
    Milestone state filter.

    Example:
        milestones = service.get_milestones(state=MilestoneState.OPEN)
    """

    OPEN = "open"
    CLOSED = "closed"
    ALL = "all"

RepositoryService

Service for repository operations.

Provides high-level methods for working with issues and milestones. Converts PyGithub objects to typed dataclasses for clean API.

Example

from src.shared_services.cloud_com.github_com.api import ( RepositoryService, IssueState, )

service = RepositoryService("octocat", "Hello-World")

Get open issues

issues = service.get_issues(state=IssueState.OPEN) for issue in issues: print(f"#{issue.number}: {issue.title}")

Get issue details

issue = service.get_issue(42) comments = service.get_issue_comments(42)

Source code in src\shared_services\cloud_com\github_com\repository.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
class RepositoryService:
    """
    Service for repository operations.

    Provides high-level methods for working with issues and milestones.
    Converts PyGithub objects to typed dataclasses for clean API.

    Example:
        from src.shared_services.cloud_com.github_com.api import (
            RepositoryService,
            IssueState,
        )

        service = RepositoryService("octocat", "Hello-World")

        # Get open issues
        issues = service.get_issues(state=IssueState.OPEN)
        for issue in issues:
            print(f"#{issue.number}: {issue.title}")

        # Get issue details
        issue = service.get_issue(42)
        comments = service.get_issue_comments(42)
    """

    def __init__(
        self,
        owner: str,
        repo: str,
        logger: Optional["AsyncAppLogger"] = None,
    ) -> None:
        """
        Initialize repository service.

        Args:
            owner: Repository owner (user or organization).
            repo: Repository name.
            logger: Optional logger instance.

        Example:
            service = RepositoryService("GehaAnlagenbauGmbH", "GehaSoftwareHubRI")
        """
        self._owner = owner
        self._repo = repo
        self._logger = logger
        self._client = GitHubClient.instance()

    def get_issues(
        self,
        state: IssueState = IssueState.OPEN,
        labels: Optional[List[str]] = None,
        milestone: Optional[str] = None,
        assignee: Optional[str] = None,
    ) -> List[GitHubIssue]:
        """
        Get repository issues.

        Automatically filters out pull requests.

        Args:
            state: Filter by state (OPEN, CLOSED, ALL).
            labels: Filter by label names.
            milestone: Filter by milestone title or number.
            assignee: Filter by assignee username.

        Returns:
            List of GitHubIssue objects.

        Raises:
            GitHubNotFoundError: If repository does not exist.
            GitHubConnectionError: If network error occurs.

        Example:
            # All open issues
            issues = service.get_issues()

            # Closed bugs
            bugs = service.get_issues(
                state=IssueState.CLOSED,
                labels=["bug"],
            )

            # Issues in milestone
            sprint_issues = service.get_issues(milestone="Sprint 1")
        """
        repo = self._client.get_repository(self._owner, self._repo)

        kwargs: Dict[str, Any] = {"state": state.value}

        if labels:
            kwargs["labels"] = labels

        if milestone:
            kwargs["milestone"] = milestone

        if assignee:
            kwargs["assignee"] = assignee

        try:
            issues_paginated = repo.get_issues(**kwargs)
            issues: List[GitHubIssue] = []

            for issue in issues_paginated:
                # Skip pull requests (they appear as issues in GitHub API)
                if issue.pull_request is not None:
                    continue

                issues.append(self._convert_issue(issue))

            return issues

        except GithubException as e:
            RepositoryService._handle_exception(e)  # Always raises

    def get_issue(self, issue_number: int) -> GitHubIssue:
        """
        Get a single issue by number.

        Args:
            issue_number: Issue number.

        Returns:
            GitHubIssue object.

        Raises:
            GitHubNotFoundError: If issue does not exist.
            GitHubConnectionError: If network error occurs.

        Example:
            issue = service.get_issue(42)
            print(f"Title: {issue.title}")
            print(f"State: {issue.state}")
        """
        repo = self._client.get_repository(self._owner, self._repo)

        try:
            issue = repo.get_issue(issue_number)
            return self._convert_issue(issue)

        except GithubException as e:
            RepositoryService._handle_exception(e)  # Always raises

    def get_issue_comments(self, issue_number: int) -> List[GitHubComment]:
        """
        Get comments for an issue.

        Args:
            issue_number: Issue number.

        Returns:
            List of GitHubComment objects.

        Raises:
            GitHubNotFoundError: If issue does not exist.
            GitHubConnectionError: If network error occurs.

        Example:
            comments = service.get_issue_comments(42)
            for comment in comments:
                print(f"{comment.user.login}: {comment.body[:100]}")
        """
        repo = self._client.get_repository(self._owner, self._repo)

        try:
            issue = repo.get_issue(issue_number)
            comments_paginated = issue.get_comments()

            return [self._convert_comment(c) for c in comments_paginated]

        except GithubException as e:
            RepositoryService._handle_exception(e)  # Always raises

    def create_issue(
        self,
        title: str,
        body: str = "",
        labels: Optional[List[str]] = None,
        assignees: Optional[List[str]] = None,
        milestone: Optional[int] = None,
    ) -> GitHubIssue:
        """
        Create a new issue.

        Args:
            title: Issue title.
            body: Issue body (Markdown).
            labels: Label names to apply.
            assignees: Usernames to assign.
            milestone: Milestone number to associate.

        Returns:
            Created GitHubIssue.

        Raises:
            GitHubPermissionError: If no permission to create issues.
            GitHubConnectionError: If network error occurs.

        Example:
            issue = service.create_issue(
                title="Bug: Application crashes on startup",
                body="## Steps to reproduce\\n1. Open app\\n2. See crash",
                labels=["bug", "priority-high"],
            )
            print(f"Created issue #{issue.number}")
        """
        repo = self._client.get_repository(self._owner, self._repo)

        kwargs: Dict[str, Any] = {"title": title, "body": body}

        if labels:
            kwargs["labels"] = labels

        if assignees:
            kwargs["assignees"] = assignees

        if milestone is not None:
            kwargs["milestone"] = repo.get_milestone(milestone)

        try:
            issue = repo.create_issue(**kwargs)
            return self._convert_issue(issue)

        except GithubException as e:
            RepositoryService._handle_exception(e)  # Always raises

    def update_issue_labels(
        self,
        issue_number: int,
        labels: List[str],
    ) -> GitHubIssue:
        """
        Update labels on an issue.

        Replaces all existing labels with the provided list.

        Args:
            issue_number: Issue number.
            labels: New label names.

        Returns:
            Updated GitHubIssue.

        Raises:
            GitHubNotFoundError: If issue does not exist.
            GitHubPermissionError: If no permission to update.
            GitHubConnectionError: If network error occurs.

        Example:
            issue = service.update_issue_labels(42, ["bug", "confirmed"])
        """
        repo = self._client.get_repository(self._owner, self._repo)

        try:
            issue = repo.get_issue(issue_number)
            issue.set_labels(*labels)
            # Refresh issue to get updated state
            issue = repo.get_issue(issue_number)
            return self._convert_issue(issue)

        except GithubException as e:
            RepositoryService._handle_exception(e)  # Always raises

    def get_issue_counts(self) -> Dict[str, int]:
        """
        Get open and closed issue counts with minimal API overhead.

        Uses PyGithub's totalCount on paginated lists, which reads the
        count from pagination headers without iterating all pages.
        Note: counts include pull requests (GitHub API limitation),
        but the ratio stays consistent so it still works for change detection.

        Returns:
            Dict with 'open' and 'closed' integer counts.

        Raises:
            GitHubConnectionError: If network error occurs.
        """
        repo = self._client.get_repository(self._owner, self._repo)

        try:
            open_count = repo.get_issues(state="open").totalCount
            closed_count = repo.get_issues(state="closed").totalCount
            return {"open": open_count, "closed": closed_count}

        except GithubException as e:
            RepositoryService._handle_exception(e)

    def get_releases(
        self,
        include_prereleases: bool = False,
    ) -> List[GitHubRelease]:
        """
        Get repository releases.

        Filters out draft releases. Optionally includes prereleases.

        Args:
            include_prereleases: Whether to include prerelease versions.

        Returns:
            List of GitHubRelease objects, newest first.

        Raises:
            GitHubNotFoundError: If repository does not exist.
            GitHubConnectionError: If network error occurs.

        Example:
            releases = service.get_releases()
            for release in releases:
                print(f"{release.tag_name}: {release.name}")
        """
        repo = self._client.get_repository(self._owner, self._repo)

        try:
            releases_paginated = repo.get_releases()
            releases: List[GitHubRelease] = []

            for release in releases_paginated:
                if release.draft:
                    continue
                if not include_prereleases and release.prerelease:
                    continue
                releases.append(self._convert_release(release))

            return releases

        except GithubException as e:
            RepositoryService._handle_exception(e)

    def get_latest_release(self) -> Optional[GitHubRelease]:
        """
        Get the latest stable release.

        Returns:
            Latest non-draft, non-prerelease GitHubRelease, or None.

        Raises:
            GitHubConnectionError: If network error occurs.

        Example:
            latest = service.get_latest_release()
            if latest:
                print(f"Latest: {latest.tag_name}")
        """
        repo = self._client.get_repository(self._owner, self._repo)

        try:
            release = repo.get_latest_release()
            return self._convert_release(release)
        except GithubException as e:
            if e.status == 404:
                return None
            RepositoryService._handle_exception(e)

    def get_milestones(
        self,
        state: MilestoneState = MilestoneState.OPEN,
    ) -> List[GitHubMilestone]:
        """
        Get repository milestones.

        Args:
            state: Filter by state (OPEN, CLOSED, ALL).

        Returns:
            List of GitHubMilestone objects.

        Raises:
            GitHubNotFoundError: If repository does not exist.
            GitHubConnectionError: If network error occurs.

        Example:
            milestones = service.get_milestones()
            for ms in milestones:
                total = ms.open_issues + ms.closed_issues
                if total > 0:
                    progress = ms.closed_issues / total
                    print(f"{ms.title}: {progress:.0%}")
        """
        repo = self._client.get_repository(self._owner, self._repo)

        try:
            milestones_paginated = repo.get_milestones(state=state.value)
            return [RepositoryService._convert_milestone(m) for m in milestones_paginated]

        except GithubException as e:
            RepositoryService._handle_exception(e)  # Always raises

    @staticmethod
    def _convert_user(user: PyGithubUser) -> GitHubUser:
        """Convert PyGithub user to GitHubUser."""
        return GitHubUser(
            login=user.login,
            id=user.id,
            avatar_url=user.avatar_url or "",
        )

    @staticmethod
    def _convert_label(label: PyGithubLabel) -> GitHubLabel:
        """Convert PyGithub label to GitHubLabel."""
        return GitHubLabel(
            name=label.name,
            color=label.color,
            description=label.description or "",
        )

    @staticmethod
    def _convert_milestone(milestone: PyGithubMilestone) -> GitHubMilestone:
        """Convert PyGithub milestone to GitHubMilestone."""
        return GitHubMilestone(
            number=milestone.number,
            title=milestone.title,
            description=milestone.description or "",
            state=milestone.state,
            due_on=milestone.due_on,
            open_issues=milestone.open_issues,
            closed_issues=milestone.closed_issues,
            created_at=milestone.created_at,
            updated_at=milestone.updated_at,
            html_url=milestone.html_url,
        )

    def _convert_comment(self, comment: PyGithubComment) -> GitHubComment:
        """Convert PyGithub comment to GitHubComment."""
        return GitHubComment(
            id=comment.id,
            body=comment.body,
            user=RepositoryService._convert_user(comment.user),
            created_at=comment.created_at,
            updated_at=comment.updated_at,
            html_url=comment.html_url,
        )

    def _convert_issue(self, issue: PyGithubIssue) -> GitHubIssue:
        """Convert PyGithub issue to GitHubIssue."""
        labels = [RepositoryService._convert_label(label) for label in issue.labels]

        assignee = None
        if issue.assignee:
            assignee = RepositoryService._convert_user(issue.assignee)

        milestone = None
        if issue.milestone:
            milestone = RepositoryService._convert_milestone(issue.milestone)

        return GitHubIssue(
            number=issue.number,
            title=issue.title,
            body=issue.body or "",
            state=issue.state,
            user=RepositoryService._convert_user(issue.user),
            labels=labels,
            assignee=assignee,
            milestone=milestone,
            created_at=issue.created_at,
            updated_at=issue.updated_at,
            closed_at=issue.closed_at,
            html_url=issue.html_url,
            comments_count=issue.comments,
        )

    @staticmethod
    def _convert_release_asset(asset: PyGithubReleaseAsset) -> GitHubReleaseAsset:
        """Convert PyGithub release asset to GitHubReleaseAsset."""
        return GitHubReleaseAsset(
            name=asset.name,
            download_url=asset.browser_download_url,
            size=asset.size,
            content_type=asset.content_type,
            download_count=asset.download_count,
        )

    def _convert_release(self, release: PyGithubRelease) -> GitHubRelease:
        """Convert PyGithub release to GitHubRelease."""
        assets = [
            RepositoryService._convert_release_asset(a)
            for a in release.get_assets()
        ]

        return GitHubRelease(
            id=release.id,
            tag_name=release.tag_name,
            name=release.title or release.tag_name,
            body=release.body or "",
            draft=release.draft,
            prerelease=release.prerelease,
            author=RepositoryService._convert_user(release.author),
            created_at=release.created_at,
            published_at=release.published_at,
            html_url=release.html_url,
            assets=assets,
        )

    @staticmethod
    def _handle_exception(e: GithubException) -> NoReturn:
        """Handle GitHub exceptions with proper error types."""
        status = e.status

        if status == 403:
            raise GitHubPermissionError(f"Permission denied: {e.data}") from e

        if status == 404:
            raise GitHubNotFoundError(f"Not found: {e.data}") from e

        raise GitHubConnectionError(f"GitHub API error: {e.data}") from e

    def _log_info(self, message: str) -> None:
        """Log info message if logger available."""
        if self._logger:
            self._logger.info(message)

    def _log_warning(self, message: str) -> None:
        """Log warning message if logger available."""
        if self._logger:
            self._logger.warning(message)
__init__(owner, repo, logger=None)

Initialize repository service.

Parameters:

Name Type Description Default
owner str

Repository owner (user or organization).

required
repo str

Repository name.

required
logger Optional[AsyncAppLogger]

Optional logger instance.

None
Example

service = RepositoryService("GehaAnlagenbauGmbH", "GehaSoftwareHubRI")

Source code in src\shared_services\cloud_com\github_com\repository.py
def __init__(
    self,
    owner: str,
    repo: str,
    logger: Optional["AsyncAppLogger"] = None,
) -> None:
    """
    Initialize repository service.

    Args:
        owner: Repository owner (user or organization).
        repo: Repository name.
        logger: Optional logger instance.

    Example:
        service = RepositoryService("GehaAnlagenbauGmbH", "GehaSoftwareHubRI")
    """
    self._owner = owner
    self._repo = repo
    self._logger = logger
    self._client = GitHubClient.instance()
create_issue(title, body='', labels=None, assignees=None, milestone=None)

Create a new issue.

Parameters:

Name Type Description Default
title str

Issue title.

required
body str

Issue body (Markdown).

''
labels Optional[List[str]]

Label names to apply.

None
assignees Optional[List[str]]

Usernames to assign.

None
milestone Optional[int]

Milestone number to associate.

None

Returns:

Type Description
GitHubIssue

Created GitHubIssue.

Raises:

Type Description
GitHubPermissionError

If no permission to create issues.

GitHubConnectionError

If network error occurs.

Example

issue = service.create_issue( title="Bug: Application crashes on startup", body="## Steps to reproduce\n1. Open app\n2. See crash", labels=["bug", "priority-high"], ) print(f"Created issue #{issue.number}")

Source code in src\shared_services\cloud_com\github_com\repository.py
def create_issue(
    self,
    title: str,
    body: str = "",
    labels: Optional[List[str]] = None,
    assignees: Optional[List[str]] = None,
    milestone: Optional[int] = None,
) -> GitHubIssue:
    """
    Create a new issue.

    Args:
        title: Issue title.
        body: Issue body (Markdown).
        labels: Label names to apply.
        assignees: Usernames to assign.
        milestone: Milestone number to associate.

    Returns:
        Created GitHubIssue.

    Raises:
        GitHubPermissionError: If no permission to create issues.
        GitHubConnectionError: If network error occurs.

    Example:
        issue = service.create_issue(
            title="Bug: Application crashes on startup",
            body="## Steps to reproduce\\n1. Open app\\n2. See crash",
            labels=["bug", "priority-high"],
        )
        print(f"Created issue #{issue.number}")
    """
    repo = self._client.get_repository(self._owner, self._repo)

    kwargs: Dict[str, Any] = {"title": title, "body": body}

    if labels:
        kwargs["labels"] = labels

    if assignees:
        kwargs["assignees"] = assignees

    if milestone is not None:
        kwargs["milestone"] = repo.get_milestone(milestone)

    try:
        issue = repo.create_issue(**kwargs)
        return self._convert_issue(issue)

    except GithubException as e:
        RepositoryService._handle_exception(e)  # Always raises
get_issue(issue_number)

Get a single issue by number.

Parameters:

Name Type Description Default
issue_number int

Issue number.

required

Returns:

Type Description
GitHubIssue

GitHubIssue object.

Raises:

Type Description
GitHubNotFoundError

If issue does not exist.

GitHubConnectionError

If network error occurs.

Example

issue = service.get_issue(42) print(f"Title: {issue.title}") print(f"State: {issue.state}")

Source code in src\shared_services\cloud_com\github_com\repository.py
def get_issue(self, issue_number: int) -> GitHubIssue:
    """
    Get a single issue by number.

    Args:
        issue_number: Issue number.

    Returns:
        GitHubIssue object.

    Raises:
        GitHubNotFoundError: If issue does not exist.
        GitHubConnectionError: If network error occurs.

    Example:
        issue = service.get_issue(42)
        print(f"Title: {issue.title}")
        print(f"State: {issue.state}")
    """
    repo = self._client.get_repository(self._owner, self._repo)

    try:
        issue = repo.get_issue(issue_number)
        return self._convert_issue(issue)

    except GithubException as e:
        RepositoryService._handle_exception(e)  # Always raises
get_issue_comments(issue_number)

Get comments for an issue.

Parameters:

Name Type Description Default
issue_number int

Issue number.

required

Returns:

Type Description
List[GitHubComment]

List of GitHubComment objects.

Raises:

Type Description
GitHubNotFoundError

If issue does not exist.

GitHubConnectionError

If network error occurs.

Example

comments = service.get_issue_comments(42) for comment in comments: print(f"{comment.user.login}: {comment.body[:100]}")

Source code in src\shared_services\cloud_com\github_com\repository.py
def get_issue_comments(self, issue_number: int) -> List[GitHubComment]:
    """
    Get comments for an issue.

    Args:
        issue_number: Issue number.

    Returns:
        List of GitHubComment objects.

    Raises:
        GitHubNotFoundError: If issue does not exist.
        GitHubConnectionError: If network error occurs.

    Example:
        comments = service.get_issue_comments(42)
        for comment in comments:
            print(f"{comment.user.login}: {comment.body[:100]}")
    """
    repo = self._client.get_repository(self._owner, self._repo)

    try:
        issue = repo.get_issue(issue_number)
        comments_paginated = issue.get_comments()

        return [self._convert_comment(c) for c in comments_paginated]

    except GithubException as e:
        RepositoryService._handle_exception(e)  # Always raises
get_issue_counts()

Get open and closed issue counts with minimal API overhead.

Uses PyGithub's totalCount on paginated lists, which reads the count from pagination headers without iterating all pages. Note: counts include pull requests (GitHub API limitation), but the ratio stays consistent so it still works for change detection.

Returns:

Type Description
Dict[str, int]

Dict with 'open' and 'closed' integer counts.

Raises:

Type Description
GitHubConnectionError

If network error occurs.

Source code in src\shared_services\cloud_com\github_com\repository.py
def get_issue_counts(self) -> Dict[str, int]:
    """
    Get open and closed issue counts with minimal API overhead.

    Uses PyGithub's totalCount on paginated lists, which reads the
    count from pagination headers without iterating all pages.
    Note: counts include pull requests (GitHub API limitation),
    but the ratio stays consistent so it still works for change detection.

    Returns:
        Dict with 'open' and 'closed' integer counts.

    Raises:
        GitHubConnectionError: If network error occurs.
    """
    repo = self._client.get_repository(self._owner, self._repo)

    try:
        open_count = repo.get_issues(state="open").totalCount
        closed_count = repo.get_issues(state="closed").totalCount
        return {"open": open_count, "closed": closed_count}

    except GithubException as e:
        RepositoryService._handle_exception(e)
get_issues(state=IssueState.OPEN, labels=None, milestone=None, assignee=None)

Get repository issues.

Automatically filters out pull requests.

Parameters:

Name Type Description Default
state IssueState

Filter by state (OPEN, CLOSED, ALL).

OPEN
labels Optional[List[str]]

Filter by label names.

None
milestone Optional[str]

Filter by milestone title or number.

None
assignee Optional[str]

Filter by assignee username.

None

Returns:

Type Description
List[GitHubIssue]

List of GitHubIssue objects.

Raises:

Type Description
GitHubNotFoundError

If repository does not exist.

GitHubConnectionError

If network error occurs.

Example
All open issues

issues = service.get_issues()

Closed bugs

bugs = service.get_issues( state=IssueState.CLOSED, labels=["bug"], )

Issues in milestone

sprint_issues = service.get_issues(milestone="Sprint 1")

Source code in src\shared_services\cloud_com\github_com\repository.py
def get_issues(
    self,
    state: IssueState = IssueState.OPEN,
    labels: Optional[List[str]] = None,
    milestone: Optional[str] = None,
    assignee: Optional[str] = None,
) -> List[GitHubIssue]:
    """
    Get repository issues.

    Automatically filters out pull requests.

    Args:
        state: Filter by state (OPEN, CLOSED, ALL).
        labels: Filter by label names.
        milestone: Filter by milestone title or number.
        assignee: Filter by assignee username.

    Returns:
        List of GitHubIssue objects.

    Raises:
        GitHubNotFoundError: If repository does not exist.
        GitHubConnectionError: If network error occurs.

    Example:
        # All open issues
        issues = service.get_issues()

        # Closed bugs
        bugs = service.get_issues(
            state=IssueState.CLOSED,
            labels=["bug"],
        )

        # Issues in milestone
        sprint_issues = service.get_issues(milestone="Sprint 1")
    """
    repo = self._client.get_repository(self._owner, self._repo)

    kwargs: Dict[str, Any] = {"state": state.value}

    if labels:
        kwargs["labels"] = labels

    if milestone:
        kwargs["milestone"] = milestone

    if assignee:
        kwargs["assignee"] = assignee

    try:
        issues_paginated = repo.get_issues(**kwargs)
        issues: List[GitHubIssue] = []

        for issue in issues_paginated:
            # Skip pull requests (they appear as issues in GitHub API)
            if issue.pull_request is not None:
                continue

            issues.append(self._convert_issue(issue))

        return issues

    except GithubException as e:
        RepositoryService._handle_exception(e)  # Always raises
get_latest_release()

Get the latest stable release.

Returns:

Type Description
Optional[GitHubRelease]

Latest non-draft, non-prerelease GitHubRelease, or None.

Raises:

Type Description
GitHubConnectionError

If network error occurs.

Example

latest = service.get_latest_release() if latest: print(f"Latest: {latest.tag_name}")

Source code in src\shared_services\cloud_com\github_com\repository.py
def get_latest_release(self) -> Optional[GitHubRelease]:
    """
    Get the latest stable release.

    Returns:
        Latest non-draft, non-prerelease GitHubRelease, or None.

    Raises:
        GitHubConnectionError: If network error occurs.

    Example:
        latest = service.get_latest_release()
        if latest:
            print(f"Latest: {latest.tag_name}")
    """
    repo = self._client.get_repository(self._owner, self._repo)

    try:
        release = repo.get_latest_release()
        return self._convert_release(release)
    except GithubException as e:
        if e.status == 404:
            return None
        RepositoryService._handle_exception(e)
get_milestones(state=MilestoneState.OPEN)

Get repository milestones.

Parameters:

Name Type Description Default
state MilestoneState

Filter by state (OPEN, CLOSED, ALL).

OPEN

Returns:

Type Description
List[GitHubMilestone]

List of GitHubMilestone objects.

Raises:

Type Description
GitHubNotFoundError

If repository does not exist.

GitHubConnectionError

If network error occurs.

Example

milestones = service.get_milestones() for ms in milestones: total = ms.open_issues + ms.closed_issues if total > 0: progress = ms.closed_issues / total print(f"{ms.title}: {progress:.0%}")

Source code in src\shared_services\cloud_com\github_com\repository.py
def get_milestones(
    self,
    state: MilestoneState = MilestoneState.OPEN,
) -> List[GitHubMilestone]:
    """
    Get repository milestones.

    Args:
        state: Filter by state (OPEN, CLOSED, ALL).

    Returns:
        List of GitHubMilestone objects.

    Raises:
        GitHubNotFoundError: If repository does not exist.
        GitHubConnectionError: If network error occurs.

    Example:
        milestones = service.get_milestones()
        for ms in milestones:
            total = ms.open_issues + ms.closed_issues
            if total > 0:
                progress = ms.closed_issues / total
                print(f"{ms.title}: {progress:.0%}")
    """
    repo = self._client.get_repository(self._owner, self._repo)

    try:
        milestones_paginated = repo.get_milestones(state=state.value)
        return [RepositoryService._convert_milestone(m) for m in milestones_paginated]

    except GithubException as e:
        RepositoryService._handle_exception(e)  # Always raises
get_releases(include_prereleases=False)

Get repository releases.

Filters out draft releases. Optionally includes prereleases.

Parameters:

Name Type Description Default
include_prereleases bool

Whether to include prerelease versions.

False

Returns:

Type Description
List[GitHubRelease]

List of GitHubRelease objects, newest first.

Raises:

Type Description
GitHubNotFoundError

If repository does not exist.

GitHubConnectionError

If network error occurs.

Example

releases = service.get_releases() for release in releases: print(f"{release.tag_name}: {release.name}")

Source code in src\shared_services\cloud_com\github_com\repository.py
def get_releases(
    self,
    include_prereleases: bool = False,
) -> List[GitHubRelease]:
    """
    Get repository releases.

    Filters out draft releases. Optionally includes prereleases.

    Args:
        include_prereleases: Whether to include prerelease versions.

    Returns:
        List of GitHubRelease objects, newest first.

    Raises:
        GitHubNotFoundError: If repository does not exist.
        GitHubConnectionError: If network error occurs.

    Example:
        releases = service.get_releases()
        for release in releases:
            print(f"{release.tag_name}: {release.name}")
    """
    repo = self._client.get_repository(self._owner, self._repo)

    try:
        releases_paginated = repo.get_releases()
        releases: List[GitHubRelease] = []

        for release in releases_paginated:
            if release.draft:
                continue
            if not include_prereleases and release.prerelease:
                continue
            releases.append(self._convert_release(release))

        return releases

    except GithubException as e:
        RepositoryService._handle_exception(e)
update_issue_labels(issue_number, labels)

Update labels on an issue.

Replaces all existing labels with the provided list.

Parameters:

Name Type Description Default
issue_number int

Issue number.

required
labels List[str]

New label names.

required

Returns:

Type Description
GitHubIssue

Updated GitHubIssue.

Raises:

Type Description
GitHubNotFoundError

If issue does not exist.

GitHubPermissionError

If no permission to update.

GitHubConnectionError

If network error occurs.

Example

issue = service.update_issue_labels(42, ["bug", "confirmed"])

Source code in src\shared_services\cloud_com\github_com\repository.py
def update_issue_labels(
    self,
    issue_number: int,
    labels: List[str],
) -> GitHubIssue:
    """
    Update labels on an issue.

    Replaces all existing labels with the provided list.

    Args:
        issue_number: Issue number.
        labels: New label names.

    Returns:
        Updated GitHubIssue.

    Raises:
        GitHubNotFoundError: If issue does not exist.
        GitHubPermissionError: If no permission to update.
        GitHubConnectionError: If network error occurs.

    Example:
        issue = service.update_issue_labels(42, ["bug", "confirmed"])
    """
    repo = self._client.get_repository(self._owner, self._repo)

    try:
        issue = repo.get_issue(issue_number)
        issue.set_labels(*labels)
        # Refresh issue to get updated state
        issue = repo.get_issue(issue_number)
        return self._convert_issue(issue)

    except GithubException as e:
        RepositoryService._handle_exception(e)  # Always raises

get_connection_status()

Get current GitHub connection status.

Returns connection state including rate limit information.

Returns:

Type Description
ConnectionStatus

ConnectionStatus with online state and rate limits.

Example

status = get_connection_status() if status.is_online: print(f"Requests remaining: {status.rate_limit_remaining}") if status.rate_limit_reset: print(f"Resets at: {status.rate_limit_reset}") else: print(f"Offline: {status.error_message}")

Source code in src\shared_services\cloud_com\github_com\api.py
def get_connection_status() -> ConnectionStatus:
    """
    Get current GitHub connection status.

    Returns connection state including rate limit information.

    Returns:
        ConnectionStatus with online state and rate limits.

    Example:
        status = get_connection_status()
        if status.is_online:
            print(f"Requests remaining: {status.rate_limit_remaining}")
            if status.rate_limit_reset:
                print(f"Resets at: {status.rate_limit_reset}")
        else:
            print(f"Offline: {status.error_message}")
    """
    client = GitHubClient.instance()
    return client.get_connection_status()

get_github_client()

Get the GitHubClient singleton instance.

For advanced usage. Prefer using RepositoryService for normal operations.

Returns:

Type Description
GitHubClient

The GitHubClient singleton.

Raises:

Type Description
GitHubClientNotInitializedError

If not initialized.

Example

client = get_github_client() repo = client.get_repository("owner", "repo")

Source code in src\shared_services\cloud_com\github_com\api.py
def get_github_client() -> GitHubClient:
    """
    Get the GitHubClient singleton instance.

    For advanced usage. Prefer using RepositoryService for normal operations.

    Returns:
        The GitHubClient singleton.

    Raises:
        GitHubClientNotInitializedError: If not initialized.

    Example:
        client = get_github_client()
        repo = client.get_repository("owner", "repo")
    """
    client = GitHubClient.instance()
    if not client.is_initialized:
        raise GitHubClientNotInitializedError(
            "GitHub client not initialized. Call initialize_github() first."
        )
    return client

get_github_token()

Get the GitHub OAuth token.

Returns the token from the initialized client, or fetches it from the login manager if not yet initialized.

Returns:

Type Description
str

GitHub OAuth token string.

Raises:

Type Description
GitHubAuthenticationError

If no token available.

Example

token = get_github_token()

Use with external GitHub library
Source code in src\shared_services\cloud_com\github_com\api.py
def get_github_token() -> str:
    """
    Get the GitHub OAuth token.

    Returns the token from the initialized client, or fetches it
    from the login manager if not yet initialized.

    Returns:
        GitHub OAuth token string.

    Raises:
        GitHubAuthenticationError: If no token available.

    Example:
        token = get_github_token()
        # Use with external GitHub library
    """
    client = GitHubClient.instance()

    if client.is_initialized and client._token:
        return client._token

    # Fallback: get directly from login manager
    from src.shared_services.security.login_manager import unified_auth_manager

    api_keys = unified_auth_manager.get_api_keys()
    token = api_keys.get("GitHubPAT")

    if not token:
        raise GitHubAuthenticationError("No GitHub token available")

    return token

initialize_github(token=None, logger=None)

Initialize the GitHub client.

Call once at application startup, after OAuth login succeeds. If no token is provided, fetches from the OAuth system automatically.

Parameters:

Name Type Description Default
token Optional[str]

GitHub OAuth token. Auto-fetched from login_manager if None.

None
logger Optional[AsyncAppLogger]

Optional logger instance for debugging.

None

Raises:

Type Description
GitHubAuthenticationError

If token is invalid or unavailable.

Example

from src.shared_services.cloud_com.github_com.api import initialize_github

In OAuth success handler

def on_oauth_success(): initialize_github() # Auto-fetches token from OAuth

Or with explicit token

initialize_github(token="ghp_xxxx")

Source code in src\shared_services\cloud_com\github_com\api.py
def initialize_github(
    token: Optional[str] = None,
    logger: Optional["AsyncAppLogger"] = None,
) -> None:
    """
    Initialize the GitHub client.

    Call once at application startup, after OAuth login succeeds.
    If no token is provided, fetches from the OAuth system automatically.

    Args:
        token: GitHub OAuth token. Auto-fetched from login_manager if None.
        logger: Optional logger instance for debugging.

    Raises:
        GitHubAuthenticationError: If token is invalid or unavailable.

    Example:
        from src.shared_services.cloud_com.github_com.api import initialize_github

        # In OAuth success handler
        def on_oauth_success():
            initialize_github()  # Auto-fetches token from OAuth

        # Or with explicit token
        initialize_github(token="ghp_xxxx")
    """
    client = GitHubClient.instance()
    client.initialize(token=token, logger=logger)

is_github_initialized()

Check if GitHub client has been initialized.

Returns:

Type Description
bool

True if initialize_github() has been called.

Example

if not is_github_initialized(): initialize_github()

Source code in src\shared_services\cloud_com\github_com\api.py
def is_github_initialized() -> bool:
    """
    Check if GitHub client has been initialized.

    Returns:
        True if initialize_github() has been called.

    Example:
        if not is_github_initialized():
            initialize_github()
    """
    client = GitHubClient.instance()
    return client.is_initialized

is_github_online()

Quick check if GitHub API is online.

Based on last request status. Does not make a new API call. Use test_github_connection() for a fresh connectivity check.

Returns:

Type Description
bool

True if last request succeeded.

Example

if is_github_online(): issues = service.get_issues()

Source code in src\shared_services\cloud_com\github_com\api.py
def is_github_online() -> bool:
    """
    Quick check if GitHub API is online.

    Based on last request status. Does not make a new API call.
    Use test_github_connection() for a fresh connectivity check.

    Returns:
        True if last request succeeded.

    Example:
        if is_github_online():
            issues = service.get_issues()
    """
    client = GitHubClient.instance()
    return client.is_online

test_github_connection()

Test if GitHub API is accessible.

Returns:

Type Description
bool

True if connection successful.

Example

if test_github_connection(): print("GitHub API is reachable") else: print("Cannot connect to GitHub")

Source code in src\shared_services\cloud_com\github_com\api.py
def test_github_connection() -> bool:
    """
    Test if GitHub API is accessible.

    Returns:
        True if connection successful.

    Example:
        if test_github_connection():
            print("GitHub API is reachable")
        else:
            print("Cannot connect to GitHub")
    """
    try:
        client = GitHubClient.instance()
        if not client.is_initialized:
            return False
        return client.test_connection()
    except (GitHubError, ImportError):
        return False