Skip to content

Commit 8c56242

Browse files
committed
PYAPI-42 Jira: Add statuses, issues, projects, transitions
1 parent c496485 commit 8c56242

File tree

4 files changed

+112
-12
lines changed

4 files changed

+112
-12
lines changed

atlassian/__init__.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,32 @@ def request(self, method='GET', path='/', data=None,
3232
data=json.dumps(data),
3333
auth=(self.username, self.password),
3434
timeout=60)
35-
if response.status_code != 200:
36-
self.log_curl_debug(method=method, path=path, headers=headers, data=data, level=logging.WARNING)
37-
log.warning(response.json())
38-
response.raise_for_status()
39-
else:
35+
if response.status_code == 200:
4036
log.debug('Received: {0}'.format(response.json()))
37+
elif response.status_code == 204:
38+
log.debug('Received "204 No Content" response')
39+
else:
40+
self.log_curl_debug(method=method, path=path, headers=headers, data=data, level=logging.DEBUG)
41+
log.info(response.json())
42+
response.raise_for_status()
4143
return response
4244

4345
def get(self, path, data=None, headers={'Content-Type': 'application/json', 'Accept': 'application/json'}):
4446
return self.request('GET', path=path, data=data, headers=headers).json()
4547

4648
def post(self, path, data=None, headers={'Content-Type': 'application/json', 'Accept': 'application/json'}):
47-
return self.request('POST', path=path, data=data, headers=headers).json()
49+
try:
50+
return self.request('POST', path=path, data=data, headers=headers).json()
51+
except ValueError:
52+
log.debug('Received response with no content')
53+
return None
4854

4955
def put(self, path, data=None, headers={'Content-Type': 'application/json', 'Accept': 'application/json'}):
50-
return self.request('PUT', path=path, data=data, headers=headers).json()
56+
try:
57+
return self.request('PUT', path=path, data=data, headers=headers).json()
58+
except ValueError:
59+
log.debug('Received response with no content')
60+
return None
5161

5262
def delete(self, path, data=None, headers={'Content-Type': 'application/json', 'Accept': 'application/json'}):
5363
return self.request('DELETE', path=path, data=data, headers=headers).json()

atlassian/jira.py

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from requests.exceptions import HTTPError
23
from atlassian import AtlassianRestAPI
34

45

@@ -28,14 +29,14 @@ def user(self, username):
2829
def project(self, key):
2930
return self.get('/rest/api/2/project/{0}'.format(key))
3031

31-
def issue(self, key):
32-
return self.get('/rest/api/2/issue/{0}'.format(key))
32+
def issue(self, key, fields='*all'):
33+
return self.get('/rest/api/2/issue/{0}?fields={1}'.format(key, fields))
3334

3435
def issue_field_value(self, key, field):
3536
issue = self.get('/rest/api/2/issue/{0}?fields={1}'.format(key, field))
3637
return issue['fields'][field]
3738

38-
def update_issue_field(self, key, fields):
39+
def update_issue_field(self, key, fields='*all'):
3940
return self.put('/rest/api/2/issue/{0}'.format(key), data={'fields': fields})
4041

4142
def project_leaders(self):
@@ -55,3 +56,92 @@ def rename_sprint(self, sprint_id, name, start_date, end_date):
5556
'name': name,
5657
'startDate': start_date,
5758
'endDate': end_date})
59+
60+
def get_project_issuekey_last(self, project):
61+
jql = 'project = {project} ORDER BY issuekey DESC'.format(project=project)
62+
return self.jql(jql)['issues'][0]['key']
63+
64+
def get_project_issuekey_all(self, project):
65+
jql = 'project = {project} ORDER BY issuekey ASC'.format(project=project)
66+
return [issue['key'] for issue in self.jql(jql)['issues']]
67+
68+
def get_project_issues_count(self, project):
69+
jql = 'project = {project}'.format(project=project)
70+
return self.jql(jql, fields='*none')['total']
71+
72+
def get_all_project_issues(self, project, fields='*all'):
73+
jql = 'project = {project} ORDER BY key'.format(project=project)
74+
return self.jql(jql, fields=fields)['issues']
75+
76+
def issue_exists(self, issuekey):
77+
try:
78+
self.issue(issuekey, fields='*none')
79+
log.info('Issue "{issuekey}" exists'.format(issuekey=issuekey))
80+
return True
81+
except HTTPError as e:
82+
if e.response.status_code == 404:
83+
log.info('Issue "{issuekey}" does not exists'.format(issuekey=issuekey))
84+
return False
85+
else:
86+
log.info('Issue "{issuekey}" existsted, but now it\'s deleted'.format(issuekey=issuekey))
87+
return True
88+
89+
def issue_deleted(self, issuekey):
90+
try:
91+
self.issue(issuekey, fields='*none')
92+
log.info('Issue "{issuekey}" is not deleted'.format(issuekey=issuekey))
93+
return False
94+
except HTTPError:
95+
log.info('Issue "{issuekey}" is deleted'.format(issuekey=issuekey))
96+
return True
97+
98+
def issue_update(self, issuekey, fields):
99+
log.warning('Updating issue "{issuekey}" with "{summary}"'.format(issuekey=issuekey, summary=fields['summary']))
100+
url = '/rest/api/2/issue/{0}'.format(issuekey)
101+
return self.put(url, data={'fields': fields})
102+
103+
def issue_create(self, fields):
104+
log.warning('Creating issue "{summary}"'.format(summary=fields['summary']))
105+
url = '/rest/api/2/issue/'
106+
return self.post(url, data={'fields': fields})
107+
108+
def issue_create_or_update(self, fields):
109+
issuekey = fields.get('issuekey', None)
110+
111+
if not issuekey or not self.issue_exists(issuekey):
112+
log.info('Issuekey is not provided or does not exists in destination. Will attempt to create an issue')
113+
del fields['issuekey']
114+
return self.issue_create(fields)
115+
116+
if self.issue_deleted(issuekey):
117+
log.warning('Issue "{issuekey}" deleted, skipping'.format(issuekey=issuekey))
118+
return None
119+
120+
log.info('Issue "{issuekey}" exists, will update'.format(issuekey=issuekey))
121+
del fields['issuekey']
122+
return self.issue_update(issuekey, fields)
123+
124+
def get_issue_transitions(self, issuekey):
125+
url = '/rest/api/2/issue/{issuekey}?expand=transitions.fields&fields=status'.format(issuekey=issuekey)
126+
return [{'name': transition['name'], 'id': int(transition['id']), 'to': transition['to']['name']} for transition in self.get(url)['transitions']]
127+
128+
def get_status_id_from_name(self, status_name):
129+
url = '/rest/api/2/status/{name}'.format(name=status_name)
130+
return int(self.get(url)['id'])
131+
132+
def get_transition_id_to_status_name(self, issuekey, status_name):
133+
for transition in self.get_issue_transitions(issuekey):
134+
if status_name == transition['to']:
135+
return int(transition['id'])
136+
137+
def issue_transition(self, issuekey, status):
138+
return self.set_issue_status(issuekey, status)
139+
140+
def set_issue_status(self, issuekey, status_name):
141+
url = '/rest/api/2/issue/{issuekey}/transitions'.format(issuekey=issuekey)
142+
transition_id = self.get_transition_id_to_status_name(issuekey, status_name)
143+
return self.post(url, data={'transition': {'id': transition_id}})
144+
145+
def get_issue_status(self, issuekey):
146+
url = '/rest/api/2/issue/{issuekey}?fields=status'.format(issuekey=issuekey)
147+
return self.get(url)['fields']['status']['name']

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[pep8]
22
max-line-length = 939
3-
ignore = E402
3+
ignore = E402,W391
44

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
description='Python Atlassian REST API Wrapper',
1111
long_description='Python Atlassian REST API Wrapper',
1212
license='Apache License 2.0',
13-
version='0.12.5',
13+
version='0.13.1',
1414
download_url='https://github.com/MattAgile/atlassian-python-api',
1515

1616
author='Matt Harasymczuk',

0 commit comments

Comments
 (0)