"""pubsalesacc/connectors/jira.py — Jira API connector. The JIRA client is instantiated lazily on first use so that importing this module does not make a network connection. If Jira is unreachable (e.g. no VPN), only the specific function calls that need Jira will fail. """ import logging import os from dotenv import load_dotenv from jira import JIRA load_dotenv() logger = logging.getLogger(__name__) _client: JIRA | None = None def _get_client() -> JIRA: """Return the cached JIRA client, creating it on first call.""" global _client if _client is None: server = os.getenv("JIRASERVER") user_id = os.getenv("JIRAUSERID") api_key = os.getenv("JIRAAPIKEY") if not all([server, user_id, api_key]): raise EnvironmentError( "JIRASERVER, JIRAUSERID, and JIRAAPIKEY must all be set in .env" ) logger.debug("Initialising Jira client for %s", server) _client = JIRA(basic_auth=(user_id, api_key), options={"server": server}) return _client # --------------------------------------------------------------------------- # Public functions # --------------------------------------------------------------------------- def create_jira( project_id: str, summary: str, description: str, issue_type: str, assignee_id: str, ) -> str: """Create a Jira issue and assign it. Returns the issue key (e.g. 'PUB-123').""" jira = _get_client() issue = jira.create_issue( project=project_id, summary=summary, description=description, issuetype={"name": issue_type}, ) # assignee_id can be an email address or an alphanumeric account ID if "@" in assignee_id: jira.assign_issue(issue.key, assignee_id) else: issue.update(assignee={"accountId": assignee_id}) logger.info("Created Jira issue: %s", issue.key) return issue.key def add_comment(issue_key: str, comment: str) -> None: _get_client().add_comment(issue_key, comment) def get_status(issue_key: str) -> str: return _get_client().issue(issue_key).fields.status.name def set_status(issue_key: str, status: str) -> None: jira = _get_client() possible = [t.get("name") for t in jira.transitions(issue_key)] if status in possible: jira.transition_issue(issue_key, status) logger.info("%s status set to '%s'.", issue_key, status) else: logger.warning( "'%s' is not a valid transition for %s. Options: %s", status, issue_key, possible, ) def add_labels(issue_key: str, labels: list[str]) -> None: _get_client().issue(issue_key).update(fields={"labels": labels}) def get_issue(issue_key: str): return _get_client().issue(issue_key)