Operate Redmine using Python Redmine

Python Redmine

Python Redmine is a Python library that communicates with Redmine.

It has the following features.

・ 100% support for Redmine API function (You can even create a project) -Python 2.7, 3.4-3.7

The details are described below. https://python-redmine.com/

Installation method

It can be installed using pip or easy_install.

$ pip install python-redmine

Or

$ easy_install python-redmine

As a setting on the Redmine side, check "Enable Web service by REST API" on the API tab on the "Administration"-> "Settings" screen.

image.png

If you enable the API here, you can check the API key on the personal settings screen. image.png

sample

A sample is described below. This sample is confirmed by Python3.7 + python redmine2.2.1 + Redmine 4.0.3.stable on Windows.

Authentication method

You can connect to redmine by specifying a user name and password.

from redminelib import Redmine
redmine = Redmine('http://localhost/redmine', username='admin', password='admin')

Alternatively, you can connect using the API key as follows.

from redminelib import Redmine
redmine = Redmine('http://localhost/redmine', key='e4a413a3b7a3c238102b7393c035bbc5f5eb6409')

Ticket operation

See below for ticket operations https://python-redmine.com/resources/issue.html

Creating a ticket

You can create a ticket by creating a ticket object with redmine.issue.new (), setting properties there, and saving it.

import datetime
from redminelib import Redmine
redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
issue = redmine.issue.new()
issue.project_id = 'Test1'
issue.subject = 'Subject'
issue.tracker_id = 1     #Tracker
issue.description = 'Show the contents of the ticket.\n Line breaks are also possible.'
issue.status_id = 1      #status
issue.priority_id = 1    #priority
issue.assigned_to_id = 1 #Person in charge ID
issue.watcher_user_ids = [1] #ID of the user to watch
issue.parent_issue_id = 12     #Parent ticket ID
issue.start_date = datetime.date(2014, 1, 1) #start date
issue.due_date = datetime.date(2014, 2, 1)   #Deadline
issue.estimated_hours = 4   #Expected man-hours
issue.done_ratio = 40
issue.custom_fields = [{'id': 1, 'value': 'foo'}]
issue.uploads = [{'path': 'C:\\dev\\python3\\redmine\\test.txt'}]
issue.custom_fields = [{'id': 1, 'value': 'foo'}]
issue.save()

Get a single ticket

Specify the ticket ID with redmine.issue.get (). If it does not exist redmine.exceptions.ResourceNotFoundError Exception occurs.

import datetime
from redminelib import Redmine
from redminelib.exceptions import ResourceNotFoundError

redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
try:
    issue = redmine.issue.get(60)
    print (dir(issue))
    print ('id:%d' % issue.id)
    print ('project:%s' % issue.project.name)
    print ('project_id:%d' % issue.project.id)
    print ('subject:%s' % issue.subject)
    print ('tracker:%s' % issue.tracker.name)
    print ('tracker_id:%d' % issue.tracker.id)
    print ('description:%s' % issue.description)
    print ('status:%s' % issue.status.name)
    print ('status:%d' % issue.status.id)
    print ('author:%s' % issue.author.name)
    print ('author_id:%d' % issue.author.id)
    if hasattr(issue, 'assigned'):
        print ('assigned:%s' % issue.assigned_to.name)
        print ('assigned_id:%d' % issue.assigned_to.id)
    print ('watcher--------')
    for u in issue.watchers:
        print (' %d:%s' % (u.id, u.name))
    print ('Created date:%s' % issue.created_on)
    print ('Update date:%s' % issue.updated_on)
    if hasattr(issue, 'start_date'):
        print ('start_date:%s' % issue.start_date)
    if hasattr(issue, 'due_date'):
        print ('issue_date:%s' % issue.due_date)
    if hasattr(issue, 'issue.estimated_hours'):
        print ('estimated_hours:%d' % issue.estimated_hours)
    print ('Working hours:%d' % issue.spent_hours)
    print ('Record of working time----------')
    for t in issue.time_entries:
        print('  ID:%d' % t.id)
        print('Activities:%s' % t.activity)
        print('comment:%s' % str(t.comments))
        print('Created date:%s' % t.created_on)
        print('time:%s' %t.hours)
        print('Ticket ID:%s' % t.issue)
        print('Project ID:%s' % t.project)
        print('date:%s' % t.spent_on)
        print('Update date:%s' % t.updated_on)
        print('  user:%d %s' % (t.user.id,t.user.name)) 
    print ('done_ratio:%d' % issue.done_ratio) 
    print ('priority:%s' % issue.priority.name)
    print ('priority_id:%d' % issue.priority.id)
    print ('custom_fields----')
    for c in issue.custom_fields:
        print ('  %d:%s = %s' % (c.id, c.name, c.value))
    print ('attachements---')
    for f in issue.attachments:
        print ('  id:%d' % (f.id))
        print ('  author:%s' % (f.author)) 
        print ('  content_url:%s' % (f.content_url))
        print ('  created_on:%s' % (f.created_on))
        print ('  description:%s' % (f.description))
        print ('  filename:%s' % (f.filename))
        print ('  filesize:%d' % (f.filesize))
        print ('  ---------------')
    print ('changeset---')
    for c in issue.changesets:
        #Commit log stored as dictionary type
        print ('  %s' % c)
    if hasattr(issue, 'parent'):
        print ('parent:%s' % issue.parent)
    print ('children----------')
    for c in issue.children:
        print ('  %s:%s' % (c.id, c.subject))
    print ('relation----------')
    for r in issue.relations:
        print ('  %d:%d->%d(%s)' % (r.id, r.issue_id, r.issue_to_id, r.relation_type)) 
except (ResourceNotFoundError):
    print ('Not found')

The property itself does not exist for the item for which input is omitted. Therefore, you have to check the existence with hasattr.

The user information of watchers, the person in charge, and the creator contains only the minimum necessary information. If you want to get the login name etc., get the user details from the user ID as follows.

user = redmine.user.get(u.id)

Get all tickets

You can get it with redmine.issue.all (). The parameters that can be omitted are as follows.

sort (string): sort order limit (integer): Upper limit of the number of acquisitions offset (integer): Acquisition start position

import datetime
from redminelib import Redmine

redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
issues = redmine.issue.all(sort='category:desc')
for issue in issues:
  print ('%d:%s' % (issue.id, issue.subject))

Obtaining a ticket for specific conditions

Tickets with specific conditions can be extracted using redmine.issue.filter. In the following, the person in charge is extracting his / her ticket.

import datetime
from redminelib import Redmine

redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
issues = redmine.issue.filter(assigned_to_id='me')
for issue in issues:
  print ('%d:%s' % (issue.id, issue.subject))

You can also search by registered query by using query_id.

Operator for query

After all, you're just reading the REST API, so you can use it with Redmine-supported operators. http://www.redmine.org/projects/redmine/wiki/Rest_Issues

In the above document, examples such as "! \ " ">" "<" "" "~" Are introduced.

So, for example, to get a task that is not assigned to anyone, the implementation is as follows.

import datetime
from redminelib import Redmine

redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
issues = redmine.issue.filter(assigned_to_id='!*')
for issue in issues:
  print ('%d:%s' % (issue.id, issue.subject))

Also, if you want to get the task of a person in charge other than yourself, it will be as follows.

import datetime
from redminelib import Redmine

redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
issues = redmine.issue.filter(assigned_to_id='!me')
for issue in issues:
  print ('%d:%s' % (issue.id, issue.subject))

We can see that various conditioning is possible by using operators such as negation and range specification, but the problem is that the REST API documentation does not tell us what kind of operator there is.

Therefore, it is necessary to refer to the actual code.

redmine/app/models/query.rb https://github.com/redmine/redmine/blob/9746ab7e5be2db5e2d233ee37365cf21ba4b893a/app/models/query.rb#L254

If you look at this implementation, you can see what operators are defined. However, not all items can be used by all operators. For example, in subject, "\ *: all", "! \ *: None" and "~: include" can be used, but "^: starts with" cannot be used. Whether or not this can be used is the same as the conditions that can be done on the filter setting screen below. image.png

Ticket renewal

Tickets obtained with redmine.issue.get can be changed and saved. The following example is an example of changing the ticket status.

# -*- coding: utf-8 -*-
import datetime
from redmine import Redmine

redmine = Redmine('http://localhost/redmine', key='e4a413a3b7a3c238102b7393c035bbc5f5eb6409')
issue = redmine.issue.get(51)
issue.status_id = 2
issue.save()

Working time manipulation

See below for working time operations https://python-redmine.com/resources/time_entry.html

Get working time

The following is an example of enumerating working hours.

import datetime
from redminelib import Redmine

redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
time_entries = redmine.time_entry.all()
for t in time_entries:
    print('  ID:%d' % t.id)
    print('Activities:%s' % t.activity)
    print('comment:%s' % str(t.comments))
    print('Created date:%s' % t.created_on)
    print('time:%s' %t.hours)
    print('Ticket ID:%s' % t.issue)
    print('Project ID:%s' % t.project)
    print('date:%s' % t.spent_on)
    print('Update date:%s' % t.updated_on)
    print('  user:%d %s' % (t.user.id,t.user.name))
    print('Work classification%d %s' % (t.activity.id, t.activity.name))

Record of working time

An example of recording the working time is shown below.

import datetime
from redminelib import Redmine

redmine = Redmine('http://192.168.0.200/', key='60076966cebf71506ae3f2391da649235a2b1d46')
time_entry = redmine.time_entry.new()
time_entry.issue_id = 60
time_entry.spent_on = datetime.date(2014, 1, 14)
time_entry.hours = 3
time_entry.activity_id = 4
time_entry.comments = 'hello'
time_entry.activity_id = 8
time_entry.save()

Summary

This time, only a part of the provided API was verified, but it was confirmed that the operations that can be performed on the Web screen are covered.

By using Python Redmine, you can expect to be able to operate Redmine freely with Python. It can be expected that the following can be realized.

-Issue a ticket from the program when the automatic test fails. ・ Automatic conversion of design documents written in Excel to Wiki -Aggregate the work time and notify another system.

that's all

Recommended Posts

Operate Redmine using Python Redmine
Start using Python
Scraping using Python
Try to operate Excel using Python (Xlwings)
Operate Filemaker from Python
Fibonacci sequence using Python
Data analysis using Python 0
Operate Kinesis with Python
Data cleaning using Python
Operate neutron from Python!
Using Python #external packages
WiringPi-SPI communication using Python
Operate Blender with Python
Age calculation using python
Operate LXC from Python
Operate the schedule app using python from iphone
Search Twitter using Python
Name identification using python
Notes using Python subprocesses
Try using Tweepy [Python2.7]
Operate Excel with Python (1)
Operate Excel with Python (2)
Register a ticket with redmine API using python requests
Python notes using perl-ternary operator
Scraping using Python 3.5 async / await
Operate Excel with Python openpyxl
Save images using python3 requests
Operate TwitterBot with Lambda, Python
[S3] CRUD with S3 using Python [Python]
[Python] Try using Tkinter's canvas
Using Quaternion with Python ~ numpy-quaternion ~
Try using Kubernetes Client -Python-
Python notes using perl-special variables
[Python] Using OpenCV with Python (Basic)
Scraping using Python 3.5 Async syntax
Website change monitoring using python
[Note] Operate MongoDB with Python
Post to Twitter using Python
Start to Selenium using python
Search algorithm using word2vec [python]
Change python version using pyenv
python: Basics of using scikit-learn ①
# 1 [python3] Simple calculation using variables
Create JIRA tickets using Python
Instrument control using Python [pyvisa]
Manipulate spreadsheets locally using Python
Python memo using perl --join
Web scraping using Selenium (Python)
[Python] I tried using OpenPose
[Python] JSON validation using Voluptuous
[Python] [SQLite3] Operate SQLite with Python (Basic)
Broadcast on LINE using python
Data analysis using python pandas
Translate using googletrans in Python
Using Python mode in Processing
Using OpenCV with Python @Mac
[Python] Shooting game using pyxel
Register redmine issue from Python
Send using Python with Gmail
Try to operate an Excel file using Python (Pandas / XlsxWriter) ①
Try to operate an Excel file using Python (Pandas / XlsxWriter) ②