Jira in Python

Create, query and update Jira tickets from Python services.

Your services work with Jira tickets in two ways:

  • Jira cloud connector: a high-level client through self.cloud.jira - use it for JQL queries and other common operations
  • Direct REST calls: for operations the connector does not cover, such as Service Desk endpoints

Create a connection

To create a connection, go to Cloud > Atlassian > Jira in the Dashboard, click Create a new connection and fill in the form:

  1. Name: My Jira
  2. Address: your Jira instance's address, e.g. https://example.atlassian.net
  3. Username: the account the connection works as, e.g. api-user@example.com
  4. Click OK

Query tickets

Use JQL (Jira Query Language) to search for issues. The connector returns structured data you can process in Python.

# -*- coding: utf-8 -*-

from zato.server.service import Service

class GetOpenTickets(Service):
    name = 'my.jira.get-open-tickets'

    def handle(self):

        # Name of your Jira connection defined in Dashboard
        conn_name = 'My Jira'

        # Fields to retrieve
        fields = ['key', 'summary', 'status', 'assignee', 'created']

        # Get a reference to the Jira connection
        jira = self.cloud.jira[conn_name]

        # Obtain a client
        with jira.conn.client() as client:

            # Build and execute JQL query
            query = 'status="Open" AND project="MYPROJECT"'
            result = client.jql(jql=query, fields=fields)

            # Process results
            issues = result['issues']
            for issue in issues:
                key = issue['key']
                summary = issue['fields']['summary']
                self.logger.info(f'Found issue: {key} - {summary}')

        self.response.payload = {'count': len(issues)}

Transition issues

To move an issue through its workflow, post the transition's ID to the issue's transitions endpoint:

# -*- coding: utf-8 -*-

import os
from json import dumps
import requests
from zato.server.service import Service

class TransitionIssue(Service):
    name = 'my.jira.transition-issue'

    input = 'ticket_id', 'transition_id'

    def handle(self):

        # Jira credentials
        base_url = 'https://yourcompany.atlassian.net'
        username = 'your.email@company.com'
        password = os.environ['JIRA_API_TOKEN']
        auth = (username, password)

        # Build the transition request
        url = f'{base_url}/rest/api/2/issue/{self.request.input.ticket_id}/transitions'
        data = {'transition': {'id': self.request.input.transition_id}}
        headers = {'Content-Type': 'application/json'}

        # Execute the transition
        response = requests.post(url, data=dumps(data), auth=auth, headers=headers)

        if not str(response.status_code).startswith('2'):
            raise Exception(f'Transition failed: {response.status_code} {response.text}')

        self.response.payload = {'status': 'transitioned'}

Add comments

To comment on an issue, post the text to the issue's comment endpoint - the public flag decides whether Service Desk customers see the comment or only agents do:

# -*- coding: utf-8 -*-

import os
from json import dumps
import requests
from zato.server.service import Service

class AddComment(Service):
    name = 'my.jira.add-comment'

    input = 'ticket_id', 'comment', '-public'

    def handle(self):

        # Jira credentials
        base_url = 'https://yourcompany.atlassian.net'
        username = 'your.email@company.com'
        password = os.environ['JIRA_API_TOKEN']
        auth = (username, password)

        # For Service Desk, use the service desk API
        url = f'{base_url}/rest/servicedeskapi/request/{self.request.input.ticket_id}/comment'

        # An unset optional flag means an internal comment
        public = self.request.input.public
        if public == '':
            public = False

        data = {
            'body': self.request.input.comment,
            'public': public
        }

        headers = {'Content-Type': 'application/json'}

        response = requests.post(url, data=dumps(data), auth=auth, headers=headers)

        if not str(response.status_code).startswith('2'):
            raise Exception(f'Comment failed: {response.status_code} {response.text}')

        self.response.payload = {'status': 'commented'}

Download attachments

To download an attachment, request its content endpoint - Jira replies with a redirect whose Location header carries the file's actual URL:

# -*- coding: utf-8 -*-

import os
import requests
from zato.server.service import Service

class GetAttachment(Service):
    name = 'my.jira.get-attachment'

    input = 'attachment_id'

    def handle(self):

        # Jira credentials
        base_url = 'https://yourcompany.atlassian.net'
        username = 'your.email@company.com'
        password = os.environ['JIRA_API_TOKEN']
        auth = (username, password)

        # Get attachment metadata and download URL
        url = f'{base_url}/rest/api/3/attachment/content/{self.request.input.attachment_id}'

        # Don't follow redirects - we want the Location header
        response = requests.get(url, auth=auth, allow_redirects=False)

        # The actual file is at the redirect location
        download_url = response.headers['Location']

        # Now download the file
        file_response = requests.get(download_url)

        self.response.payload = {
            'content': file_response.content,
            'size': len(file_response.content)
        }

Use the Jira cloud connector

The connector's client covers common operations directly - single issues by key, JQL searches and the rest of Jira's API:

# -*- coding: utf-8 -*-

from zato.server.service import Service

class JiraConnectorExample(Service):
    name = 'my.jira.connector-example'

    def handle(self):

        # Get the Jira connection
        jira = self.cloud.jira['My Jira']

        with jira.conn.client() as client:

            # Get a single issue
            issue = client.get_issue('PROJ-123', fields=['summary', 'status'])

            # Search with JQL
            results = client.jql(
                jql='project=PROJ AND status=Open',
                fields=['key', 'summary']
            )

            # The client provides direct access to Jira's API
            self.response.payload = {
                'issue': issue,
                'search_count': len(results['issues'])
            }

See also

FeatureWhat it does
REST outgoing connectionsCall Jira REST endpoints the connector does not cover
SlackNotify people when tickets change
SchedulerPoll Jira for changes on an interval

Learn more