File: examples.rst

package info (click to toggle)
python-jira 3.9.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,008 kB
  • sloc: python: 8,643; sh: 13; makefile: 7; xml: 4
file content (474 lines) | stat: -rw-r--r-- 16,627 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
Examples
********

Here's a quick usage example:

.. literalinclude:: ../examples/basic_use.py

Another example with methods to authenticate with your Jira:

.. literalinclude:: ../examples/auth.py

This example shows how to work with Jira Agile / Jira Software (formerly GreenHopper):

.. literalinclude:: ../examples/agile.py


Quickstart
==========

Initialization
--------------

Everything goes through the :py:class:`jira.client.JIRA` object, so make one::

    from jira import JIRA

    jira = JIRA()

This connects to a Jira started on your local machine at http://localhost:2990/jira, which not coincidentally is the
default address for a Jira instance started from the Atlassian Plugin SDK.

You can manually set the Jira server to use::

    jira = JIRA('https://jira.atlassian.com')

Authentication
--------------

At initialization time, jira-python can optionally create an HTTP BASIC or use OAuth 1.0a access tokens for user
authentication. These sessions will apply to all subsequent calls to the  :py:class:`jira.client.JIRA` object.

The library is able to load the credentials from inside the ~/.netrc file, so put them there instead of keeping them in your source code.

Cookie Based Authentication
^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. warning::
    This method of authentication is no longer supported on Jira Cloud. You can find the deprecation notice `here <https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth>`_.

    For Jira Cloud use the basic_auth= :ref:`basic-auth-api-token` authentication

Pass a tuple of (username, password) to the ``auth`` constructor argument::

    auth_jira = JIRA(auth=('username', 'password'))

Using this method, authentication happens during the initialization of the object. If the authentication is successful,
the retrieved session cookie will be used in future requests. Upon cookie expiration, authentication will happen again transparently.


HTTP BASIC
^^^^^^^^^^

(username, password)
""""""""""""""""""""

.. warning::
    This method of authentication is no longer supported on Jira Cloud. You can find the deprecation notice `here <https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth>`_

    For Jira Cloud use the basic_auth= :ref:`basic-auth-api-token` authentication.
    For Self Hosted Jira (Server, Data Center), consider the `Token Auth`_ authentication.

Pass a tuple of (username, password) to the ``basic_auth`` constructor argument::

    auth_jira = JIRA(basic_auth=('username', 'password'))

.. _basic-auth-api-token:

(username, api_token)
"""""""""""""""""""""


Or pass a tuple of (email, api_token) to the ``basic_auth`` constructor argument (JIRA Cloud)::

    auth_jira = JIRA(basic_auth=('email', 'API token'))

.. seealso::
    For Self Hosted Jira (Server, Data Center), refer to the `Token Auth`_ Section.


OAuth
^^^^^

Pass a dict of OAuth properties to the ``oauth`` constructor argument::

    # all values are samples and won't work in your code!
    key_cert_data = None
    with open(key_cert, 'r') as key_cert_file:
        key_cert_data = key_cert_file.read()

    oauth_dict = {
        'access_token': 'foo',
        'access_token_secret': 'bar',
        'consumer_key': 'jira-oauth-consumer',
        'key_cert': key_cert_data
    }
    auth_jira = JIRA(oauth=oauth_dict)

.. note ::
    The OAuth access tokens must be obtained and authorized ahead of time through the standard OAuth dance. For
    interactive use, ``jirashell`` can perform the dance with you if you don't already have valid tokens.

* The access token and token secret uniquely identify the user.
* The consumer key must match the OAuth provider configured on the Jira server.
* The key cert data must be the private key that matches the public key configured on the Jira server's OAuth provider.

See https://confluence.atlassian.com/display/JIRA/Configuring+OAuth+Authentication+for+an+Application+Link for details
on configuring an OAuth provider for Jira.

Token Auth
^^^^^^^^^^


Jira Cloud
""""""""""

This is also referred to as an API Token in the
`Jira Cloud documentation <https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/>`_ ::

    auth_jira = JIRA(basic_auth=('email', 'API token'))


Jira Self Hosted (incl. Jira Server/Data Center)
""""""""""""""""""""""""""""""""""""""""""""""""

This is also referred to as Personal Access Tokens (PATs) in the
`Self-Hosted Documentation <https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html>`_.
The is available from Jira Core >= 8.14::

    auth_jira = JIRA(token_auth='API token')


Kerberos
^^^^^^^^

To enable Kerberos auth, set ``kerberos=True``::

    auth_jira = JIRA(kerberos=True)

To pass additional options to Kerberos auth use dict ``kerberos_options``, e.g.::

    auth_jira = JIRA(kerberos=True, kerberos_options={'mutual_authentication': 'DISABLED'})

.. _jirashell-label:

Headers
-------

Headers can be provided to the internally used ``requests.Session``.
If the user provides a header that the :py:class:`jira.client.JIRA` also attempts to set, the user provided header will take preference.

For example if you want to use a custom User Agent::

    from requests_toolbelt import user_agent

    jira = JIRA(
        basic_auth=("email", "API token"),
        options={"headers": {"User-Agent": user_agent("my_package", "0.0.1")}},
    )

Issues
------

Issues are objects. You get hold of them through the ``JIRA`` object::

    issue = jira.issue('JRA-1330')

Issue JSON is marshaled automatically and used to augment the returned Issue object, so you can get direct access to
fields::

    summary = issue.fields.summary         # 'Field level security permissions'
    votes = issue.fields.votes.votes       # 440 (at least)

If you only want a few specific fields, save time by asking for them explicitly::

    issue = jira.issue('JRA-1330', fields='summary,comment')

Reassign an issue::

    # requires issue assign permission, which is different from issue editing permission!
    jira.assign_issue(issue, 'newassignee')

If you want to unassign it again, just do::

    jira.assign_issue(issue, None)

Creating issues is easy::

    new_issue = jira.create_issue(project='PROJ_key_or_id', summary='New issue from jira-python',
                                  description='Look into this one', issuetype={'name': 'Bug'})

Or you can use a dict::

    issue_dict = {
        'project': {'id': 123},
        'summary': 'New issue from jira-python',
        'description': 'Look into this one',
        'issuetype': {'name': 'Bug'},
    }
    new_issue = jira.create_issue(fields=issue_dict)

You can even bulk create multiple issues::

    issue_list = [
    {
        'project': {'id': 123},
        'summary': 'First issue of many',
        'description': 'Look into this one',
        'issuetype': {'name': 'Bug'},
    },
    {
        'project': {'key': 'FOO'},
        'summary': 'Second issue',
        'description': 'Another one',
        'issuetype': {'name': 'Bug'},
    },
    {
        'project': {'name': 'Bar'},
        'summary': 'Last issue',
        'description': 'Final issue of batch.',
        'issuetype': {'name': 'Bug'},
    }]
    issues = jira.create_issues(field_list=issue_list)

.. note::
    Project, summary, description and issue type are always required when creating issues. Your Jira may require
    additional fields for creating issues; see the ``jira.createmeta`` method for getting access to that information.

.. note::
    Using bulk create will not throw an exception for a failed issue creation. It will return a list of dicts that
    each contain a possible error signature if that issue had invalid fields. Successfully created issues will contain
    the issue object as a value of the ``issue`` key.

You can also update an issue's fields with keyword arguments::

    issue.update(summary='new summary', description='A new summary was added')
    issue.update(assignee={'name': 'new_user'})    # reassigning in update requires issue edit permission

or with a dict of new field values::

    issue.update(fields={'summary': 'new summary', 'description': 'A new summary was added'})

You can suppress notifications::

    issue.update(notify=False, description='A quiet description change was made')

and when you're done with an issue, you can send it to the great hard drive in the sky::

    issue.delete()

Updating components::

    existingComponents = []
    for component in issue.fields.components:
        existingComponents.append({"name" : component.name})
    issue.update(fields={"components": existingComponents})

Working with Rich Text
^^^^^^^^^^^^^^^^^^^^^^

You can use rich text in an issue's description or comment. In order to use rich text, the body
content needs to be formatted using the Atlassian Document Format (ADF)::

    jira = JIRA(basic_auth=("email", "API token"))
    comment = {
        "type": "doc",
        "version": 1,
        "content": [
          {
            "type": "codeBlock",
            "content": [
              {
                "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.",
                "type": "text"
              }
            ]
          }
        ]
      }
    jira.add_comment("AB-123", comment)

Fields
------

Example for accessing the worklogs::

    issue.fields.worklog.worklogs                                 # list of Worklog objects
    issue.fields.worklog.worklogs[0].author
    issue.fields.worklog.worklogs[0].comment
    issue.fields.worklog.worklogs[0].created
    issue.fields.worklog.worklogs[0].id
    issue.fields.worklog.worklogs[0].self
    issue.fields.worklog.worklogs[0].started
    issue.fields.worklog.worklogs[0].timeSpent
    issue.fields.worklog.worklogs[0].timeSpentSeconds
    issue.fields.worklog.worklogs[0].updateAuthor                # dictionary
    issue.fields.worklog.worklogs[0].updated


    issue.fields.timetracking.remainingEstimate           # may be NULL or string ("0m", "2h"...)
    issue.fields.timetracking.remainingEstimateSeconds    # may be NULL or integer
    issue.fields.timetracking.timeSpent                   # may be NULL or string
    issue.fields.timetracking.timeSpentSeconds            # may be NULL or integer


Searching
---------

Leverage the power of `JQL <https://confluence.atlassian.com/display/JIRA/Advanced+Searching>`_
to quickly find the issues you want::

    # Search returns first 50 results, `maxResults` must be set to exceed this
    issues_in_proj = jira.search_issues('project=PROJ')
    all_proj_issues_but_mine = jira.search_issues('project=PROJ and assignee != currentUser()')

    # my top 5 issues due by the end of the week, ordered by priority
    oh_crap = jira.search_issues('assignee = currentUser() and due < endOfWeek() order by priority desc', maxResults=5)

    # Summaries of my last 3 reported issues
    for issue in jira.search_issues('reporter = currentUser() order by created desc', maxResults=3):
        print('{}: {}'.format(issue.key, issue.fields.summary))

Comments
--------

Comments, like issues, are objects. Access issue comments through the parent Issue object or the ``JIRA`` object's
dedicated method::

    comments_a = issue.fields.comment.comments
    comments_b = jira.comments(issue) # comments_b == comments_a

Obtain an individual comment if you know its ID::

    comment = jira.comment('JRA-1330', '10234')

Obtain comment author name and comment creation timestamp if you know its ID::

    author = jira.comment('JRA-1330', '10234').author.displayName
    time = jira.comment('JRA-1330', '10234').created

Adding, editing and deleting comments is similarly straightforward::

    comment = jira.add_comment('JRA-1330', 'new comment')    # no Issue object required
    comment = jira.add_comment(issue, 'new comment', visibility={'type': 'role', 'value': 'Administrators'})  # for admins only

    comment.update(body='updated comment body')
    comment.update(body='updated comment body but no mail notification', notify=False)
    comment.delete()

Get all images from a comment::

    issue = jira.issue('JRA-1330')
    regex_for_png = re.compile(r'\!(\S+?\.(jpg|png|bmp))\|?\S*?\!')
    pngs_used_in_comment = regex_for_png.findall(issue.fields.comment.comments[0].body)
    for attachment in issue.fields.attachment:
        if attachment.filename in pngs_used_in_comment:
            with open(attachment.filename, 'wb') as f:
                f.write(attachment.get())

Transitions
-----------

Learn what transitions are available on an issue::

    issue = jira.issue('PROJ-1')
    transitions = jira.transitions(issue)
    [(t['id'], t['name']) for t in transitions]    # [(u'5', u'Resolve Issue'), (u'2', u'Close Issue')]

.. note::
    Only the transitions available to the currently authenticated user will be returned!

Then perform a transition on an issue::

    # Resolve the issue and assign it to 'pm_user' in one step
    jira.transition_issue(issue, '5', assignee={'name': 'pm_user'}, resolution={'id': '3'})

    # The above line is equivalent to:
    jira.transition_issue(issue, '5', fields={'assignee':{'name': 'pm_user'}, 'resolution':{'id': '3'}})

Projects
--------

Projects are objects, just like issues::

    projects = jira.projects()

Also, just like issue objects, project objects are augmented with their fields::

    jra = jira.project('JRA')
    print(jra.name)                 # 'JIRA'
    print(jra.lead.displayName)     # 'John Doe [ACME Inc.]'

It's no trouble to get the components, versions or roles either (assuming you have permission)::

    components = jira.project_components(jra)
    [c.name for c in components]                # 'Accessibility', 'Activity Stream', 'Administration', etc.

    jira.project_roles(jra)                     # 'Administrators', 'Developers', etc.

    versions = jira.project_versions(jra)
    [v.name for v in reversed(versions)]        # '5.1.1', '5.1', '5.0.7', '5.0.6', etc.

Watchers
--------

Watchers are objects, represented by :class:`jira.resources.Watchers`::

    watcher = jira.watchers(issue)
    print("Issue has {} watcher(s)".format(watcher.watchCount))
    for watcher in watcher.watchers:
        print(watcher)
        # watcher is instance of jira.resources.User:
        print(watcher.emailAddress)

You can add users to watchers by their name::

    jira.add_watcher(issue, 'username')
    jira.add_watcher(issue, user_resource.name)

And of course you can remove users from watcher::

    jira.remove_watcher(issue, 'username')
    jira.remove_watcher(issue, user_resource.name)

Attachments
-----------

Attachments let user add files to issues. First you'll need an issue to which the attachment will be uploaded.
Next, you'll need the file itself that is going to be attachment. The file could be a file-like object or string, representing
path on the local machine. You can also modify the final name of the attachment if you don't like original.
Here are some examples::

    # upload file from `/some/path/attachment.txt`
    jira.add_attachment(issue=issue, attachment='/some/path/attachment.txt')

    # read and upload a file (note binary mode for opening, it's important):
    with open('/some/path/attachment.txt', 'rb') as f:
        jira.add_attachment(issue=issue, attachment=f)

    # attach file from memory (you can skip IO operations). In this case you MUST provide `filename`.
    from io import StringIO
    attachment = StringIO()
    attachment.write(data)
    jira.add_attachment(issue=issue, attachment=attachment, filename='content.txt')

If you would like to list all available attachment, you can do it with through attachment field::


    for attachment in issue.fields.attachment:
        print("Name: '{filename}', size: {size}".format(
            filename=attachment.filename, size=attachment.size))
        # to read content use `get` method:
        print("Content: '{}'".format(attachment.get()))


You can delete attachment by id::

    # Find issues with attachments:
    query = jira.search_issues(jql_str="attachments is not EMPTY", json_result=True, fields="key, attachment")

    # And remove attachments one by one
    for i in query['issues']:
        for a in i['fields']['attachment']:
            print("For issue {0}, found attach: '{1}' [{2}].".format(i['key'], a['filename'], a['id']))
            jira.delete_attachment(a['id'])