Generating a Python REST Client for Jira Cloud

You may have read my previous article about generating a Java library to call Jira’s REST APIs, but what about other languages?

Here I’ll cover doing the same with Python.

I will generate a library for Python, and then run through some sample code to create a project, find the “task” issue type in the project, and then create a issue of type “task”.

Why Generate?

There are 3rd party Python libraries around that can talk to Jira Cloud. If these work well for you, then that might be a good solution.

Atlassian publishes the Swagger/Open API spec for Jira’s API in real time though. If you need to call a new Jira Cloud API, or act on some feature that is being deprecated, then being able to generate an up-to-date library might be of value to your project.

Jira’s Open API spec

Just a quick recap: Swagger/Open API is a way of documenting APIs in a standard way. While you can always write your own code to call Jira’s APIs, the APIs change all the time in small ways. Generating a client library from the spec using a code generation tool is a quick way to get started (and easy to update in the future).

If you go to the documentation for the Jira Cloud REST API, you'll see the "…" in the top right. Click that, and you'll see the link for "Download OpenAPI Spec." This is a JSON file that describes the REST API for Jira Cloud. Here I'm using the v2 API so that I can use plain text for the issue description.

I’m going to use swagger-codegen to generate my code. (Read more here)

The actual URL to this file is https://dac-static.atlassian.com/cloud/jira/platform/swagger.v3.json?_v=1.6234.0-0.1265.0. Although swagger-codegen can generate APIs directly from a URL, I recommend saving a local copy of this file. I am going to use a local copy in my examples.

In this post I will deal with APIs common to all Jira project types, but the process is similar for Jira Software's APIs, and Jira Service Desk's APIs.

Generating Code

Once you have downloaded the swagger-v3.v3.json file, the swagger-codegen command to generate a Python client under the current directory is:

swagger-codegen generate \
-i swagger.v3.json \
-l python \
-o .

This will generate a usable client in the current directory, but it’s probably worth tweaking the configuration a little. I’m going to set the package name, and project name (which is used in setup.py).

Create a config.json file that describes your configuration overrides.

{
"packageName": "jira_client",
"projectName": "jira-cloud"
}

Now to generate code using the above config file use:

swagger-codegen generate \
-i swagger.v3.json \
-c config.json \
-l python \
-o .

What to do with the library?

Run the following to install the library in your virtual environment:

python3 setup.py install

The output directory includes a README.md file that has more details on how you might install it. You may prefer to use pip3 install foldername.

You may wish to integrate the built code directly into your project, rather that install it as its own library.

Check into version control

While the client code is generated, my recommended best practice is to version control the generated client code.

Why? This helps you to know what changed since the last time you generated it. You can re-generate it from an updated spec file, and your version control system will show you what changed.

Using the code

For my code example I’m going to assume the library was installed using the package name “jira_client”, and I can just import it directly.

In this example I'm going to

  • Look up my user

  • Create a new project where I am the project lead

  • Find out the issue type ID of “Task” issue type in my project

  • Create a new issue of type Task in my project, and display the issue key

If you have used the Java client, you will notice that the API and model classes have similar names to the Java version, except with Python naming conventions.

Note: The generated code uses a class called Configuration to specify the configuration when creating the ApiClient. This will clash with a model class called Configuration, so I’m going to import it as ApiConfiguration.

from jira_client import ApiClient
# Configuration is ambiguous
from jira_client.configuration import Configuration as ApiConfiguration
from jira_client.rest import ApiException
from jira_client import ProjectsApi, MyselfApi, IssuesApi
from jira_client.models.create_project_details import CreateProjectDetails
from pprint import pprint

configuration = ApiConfiguration()
configuration.username = 'myemail@atlassian.com'
configuration.password = 'my_application_password'
configuration.host = 'https://myjirahost.atlassian.net/'
# Turn on debug output
configuration.debug = True

api_client = ApiClient(configuration)

# Get my account ID
myself_api = MyselfApi(api_client)
current_user = myself_api.get_current_user()

# Create a new project
projects_api = ProjectsApi(api_client)
details = CreateProjectDetails(
key='JPY',
name='Jira Python',
lead_account_id=current_user.account_id,
project_type_key='business')
project = projects_api.create_project(details)

# Now do a project search, expanding issue types
paged_projects = projects_api.search_projects(
start_at=0,
max_results=100,
query='JPY',
expand="issueTypes")
projects = paged_projects.values
project_result = projects[0]
task_issue_type_id = 0
for issue_type in project_result.issue_types:
if issue_type.name == 'Task':
task_issue_type_id = issue_type.id

print(f"Task issue type is {task_issue_type_id}")

# Now create an issue using the Task issue type

issues_api = IssuesApi(api_client)

project_field = {
"id": project_result.id
}
issue_type_field = {
"id": task_issue_type_id
}

issue_fields = {
"summary": "Learn Python",
"description": "Remember to study up on Python programming",
"project": project_field,
"issuetype": issue_type_field
}

issue_body = {
"fields": issue_fields
}

issue = issues_api.create_issue(issue_body)

print(f"Issue created. Key is {issue.key}")

Now if I log in to my site I should see that there is a project with the key “JPY”, and with a “Learn Python” task.

6 comments

Oi Lee September 14, 2021

I ran into a "AttributeError: module 'jira_client.models' has no attribute 'Object'" error.

>>> jql = 'created > -1h'
>>> r = api_instance.search_for_issues_using_jql(jql=jql)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/olee/Desktop/test/jira/jira_client/api/issue_search_api.py", line 271, in search_for_issues_using_jql
(data) = self.search_for_issues_using_jql_with_http_info(**kwargs) # noqa: E501
File "/Users/olee/Desktop/test/jira/jira_client/api/issue_search_api.py", line 350, in search_for_issues_using_jql_with_http_info
return self.api_client.call_api(
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 316, in call_api
timeout. It can also be a pair (tuple) of
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 160, in __call_api
return_data = self.deserialize(response_data, response_type)
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 232, in deserialize
return self.__deserialize(data, response_type)
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 271, in __deserialize

File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 614, in __deserialize_model
if not klass.swagger_types and not self.__hasattr(klass, 'get_real_child_model'):
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 248, in __deserialize
l = []
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 248, in <listcomp>
l = []
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 271, in __deserialize

File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 614, in __deserialize_model
if not klass.swagger_types and not self.__hasattr(klass, 'get_real_child_model'):
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 253, in __deserialize
return l
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 253, in <dictcomp>
return l
File "/Users/olee/Desktop/test/jira/jira_client/api_client.py", line 260, in __deserialize
for k, v in six.iteritems(data):
AttributeError: module 'jira_client.models' has no attribute 'Object'

It appears klass.swagger_types uses "Object" in api_client.py on line 610:

for attr, attr_type in six.iteritems(klass.swagger_types):

(Pdb) klass.swagger_types
{'expand': 'str', 'id': 'str', '_self': 'str', 'key': 'str', 'rendered_fields': 'dict(str, Object)', 'properties': 'dict(str, Object)', 'names': 'dict(str, str)', 'schema': 'dict(str, JsonTypeBean)', 'transitions': 'list[IssueTransition]', 'operations': 'AllOfIssueBeanOperations', 'editmeta': 'AllOfIssueBeanEditmeta', 'changelog': 'AllOfIssueBeanChangelog', 'versioned_representations': 'dict(str, dict(str, Object))', 'fields_to_include': 'IncludedFields', 'fields': 'dict(str, Object)'}

But is checking for "object" in self.NATIVE_TYPES_MAPPING on line 258:

if klass in self.NATIVE_TYPES_MAPPING:

(Pdb) self.NATIVE_TYPES_MAPPING
{'int': <class 'int'>, 'long': <class 'int'>, 'float': <class 'float'>, 'str': <class 'str'>, 'bool': <class 'bool'>, 'date': <class 'datetime.date'>, 'datetime': <class 'datetime.datetime'>, 'object': <class 'object'>}

After I added 'Object': object into self.NATIVE_TYPES_MAPPING, I didn't get any errors.

Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
September 14, 2021

Hi @Oi Lee . That looks like this bug in swagger-codegen: https://github.com/swagger-api/swagger-codegen/issues/10129

I encountered the same error, and so I submitted a fix for this. It looks like the latest version of swagger-codegen came out in late June, but the fix for that bug didn't go in until July. Sorry about that - I assumed this fix had been released by now.

Your fix above is a good work-around. If you are able to use swagger-codegen version 3.0.28-SNAPSHOT (the latest dev version) it should work better, or perhaps just keep using this work-around until the next version of swagger-codegen is released.

Like Oi Lee likes this
Oi Lee September 15, 2021

Thanks for the quick response! I'll use the work around until they get it in a master version of swagger-codegen.

I have a quick question (can't find via Google or in the docs). I'm trying to figure out how to get the labels for the custom fields in the issue obj -> fields dict.

Another thing I noticed is that self.__hasattr() is not defined (screenshot below).Screen Shot 2021-09-14 at 3.32.04 PM.png

Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
September 15, 2021

Hmm __hasattr is part of the library code that swagger-codegen writes for you. It should be near the bottom of api_client.py.

Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
September 15, 2021

I'm not quite sure what you mean by labels. Sometimes turning on debug can be helpful to see what data is coming back, so you can check if the thing you are looking for is actually returned in the response.

e.g.

configuration = ApiConfiguration()
configuration.username = 'my@email.com'
configuration.password = 'mypassword'
configuration.host = 'https://mysite.atlassian.net/'
configuration.debug = True

Some field stuff comes back as a dict of a dict of a dict etc. Is the value you are looking for returned with the issue? The debug output might help you to know where.

It could also be that what you're after is part of the field configuration, which may not be sent with the issue (only the current value of that field).

Oi Lee September 16, 2021

From search results using the IssueSearchApi, there is a key called issues -> fields and in the fields dictionary some of the keys start with customfield_NNNNN. Instead using the field id, I would like to use the field name instead. Is there a way to do that either when I use the IssueSearchApi or after I get the search results?

Hope this make sense....

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events