A client library for working with Azure DevOps (formerly VSTS) and TFS projects, areas/iterations, sprints, work items and tasks written in Python.
Please feel free to send me a pull request if you've fixed a bug or added a feature.
pip install vsts-client
In order to connect to Azure DevOps, you need to obtain a personal access token.
# Import the VstsClient module
from vstsclient.vstsclient import VstsClient
# Initialize the VSTS client using the Azure DevOps url and personal access token
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
Or you can still use visualstudio.com.
client = VstsClient('<account>.visualstudio.com', '<personalaccesstoken>')
To connect to an on-premises TFS environment you supply the server name and port number (default is 8080).
client = VstsClient('tfs.contoso.com:8080', '<personalaccesstoken>')
The VSTS client will pick the DefaultCollection
by default. You can specify a different collection using the optional collection
parameter.
client = VstsClient('tfs.contoso.com:8080', '<personalaccesstoken>', '<your collection>')
client.set_proxy('proxy.contoso.com', 8080, '<username>', '<password>')
Get all team projects in the project collection that the authenticated user has access to.
from vstsclient.vstsclient import VstsClient
from vstsclient.constants import StateFilter
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
# StateFilter options are WellFormed (default), New, Deleting, CreatePending and All
projects = client.get_projects(StateFilter.WELL_FORMED)
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
project = client.get_project('Contoso')
Create a team project in a Azure DevOps account using the given SourceControlType
and ProcessTemplate
.
from vstsclient.vstsclient import VstsClient
from vstsclient.constants import (
ProcessTemplate,
SourceControlType
)
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
project = client.create_project(
'Contoso', # Project name
'A project description', # Project description
SourceControlType.GIT, # Source control type: Git or Tfvc
ProcessTemplate.AGILE) # Process template: Agile, Scrum or CMMI
All work items have an area and an iteration field. The values that these fields can have are defined in the classification hierarchies.
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
areas = client.get_areas('Contoso')
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
areas = client.get_areas('Contoso', 2)
for area in areas.children:
print(area.name)
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
iterations = client.get_iterations('Contoso')
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
iterations = client.get_iterations(
'Contoso', # Team project name
2) # Hierarchy depth
for iteration in iterations.children:
print(iteration.name)
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
area = client.get_area('Contoso', 'Area 1')
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
iteration = client.get_iteration('Contoso', 'Sprint 1')
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
area = client.create_area(
'Contoso', # Team project name
'Area 1') # Area name
from vstsclient.vstsclient import VstsClient
start_date = datetime.datetime.utcnow() # Sprint starts today
finish_date = start_date + datetime.timedelta(days=21) # Ends in 3 weeks
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
iteration = client.create_iteration(
'Contoso', # Team project name
'Sprint 1', # Iteration name
start_date, # Start date
finish_date) # End date
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
workitems = client.get_workitems_by_id('1,2,3,5,8,13,21,34')
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
workitem = client.get_workitem(13)
When you create a work item, you can provide values for any of the work item fields.
from vstsclient.vstsclient import VstsClient
from vstsclient.models import JsonPatchDocument, JsonPatchOperation
from vstsclient.constants import SystemFields, MicrosoftFields
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
# Create a JsonPatchDocument and provide the values for the work item fields
doc = JsonPatchDocument()
doc.add(JsonPatchOperation('add', SystemFields.TITLE, 'Work item 1'))
doc.add(JsonPatchOperation('add', SystemFields.DESCRIPTION, 'Work item description.'))
doc.add(JsonPatchOperation('add', SystemFields.TAGS, 'tag1; tag2'))
# Create a new work item by specifying the project and work item type
workitem = client.create_workitem(
'Contoso', # Team project name
'User Story', # Work item type (e.g. Epic, Feature, User Story etc.)
doc) # JsonPatchDocument with operations
from vstsclient.vstsclient import VstsClient
from vstsclient.models import JsonPatchDocument, JsonPatchOperation
from vstsclient.constants import SystemFields
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
# Create a JsonPatchDocument and provide the values for the fields to update
doc = JsonPatchDocument()
doc.add(JsonPatchOperation('replace', SystemFields.TITLE, 'Work item 2'))
# Update work item id 13
workitem = client.update_workitem(13, doc)
Only supported on Azure DevOps (not on TFS).
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
client.change_workitem_type(13, 'Task')
Only supported on Azure DevOps (not on TFS).
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
# To move a work item, provide the Team Project, Area path and Iteration path to move to
client.move_workitem(13, 'Contoso', 'Contoso', 'Sprint 1')
tags = ['Tag1', 'Tag2']
client.add_tags(13, tags)
from vstsclient.constants import LinkTypes
feature = client.get_workitem(1) # Get a feature
userstory = client.get_workitem(2) # Get a user story
# Create a parent/child link between the feature and the userstory
client.add_link(userstory.id, feature.id, LinkTypes.PARENT, 'Adding this user story to feature x')
# Note that you can create the same link the other way around
client.add_link(feature.id, userstory.id, LinkTypes.CHILD, 'Adding user story x to this feature')
To attach a file to a work item, upload the attachment to the attachment store using upload_attachment
, then attach it to the work item.
workitem = client.get_workitem(1)
attachment = None
# Upload the attachment to the attachment store
with open('./example.png', 'rb') as f:
attachment = client.upload_attachment('example.png', f)
# Link the attachment to the work item
client.add_attachment(workitem.id, attachment.url, 'Linking example.png to a work item')
Bypassing the rules engine allows you to modify work item fields without any restrictions, for example you can assign a work item to a user no longer in the organization.
To modify the
System.CreatedBy
,System.CreatedDate
,System.ChangedBy
, orSystem.ChangedDate
fields, you must be a member of the Project Collection Service Acccounts group.
doc = JsonPatchDocument()
doc.add(JsonPatchOperation('add', SystemFields.CHANGED_BY, 'Woody <woody@contoso.com>'))
doc.add(JsonPatchOperation('add', SystemFields.CHANGED_DATE, '01-01-2018'))
# Set the bypass_rules parameter to True
client.update_workitem(13, doc, bypass_rules=True)
System.CreatedBy
andSystem.CreatedDate
can only be modified using bypass rules on work item creation, i.e. the first revision of a work item.
# Set the Created By and Created Date fields
doc = JsonPatchDocument()
doc.add(JsonPatchOperation('add', SystemFields.TITLE, 'Work item 1'))
doc.add(JsonPatchOperation('add', SystemFields.DESCRIPTION, 'Work item description.'))
doc.add(JsonPatchOperation('add', SystemFields.CREATED_BY, 'Woody <woody@contoso.com>'))
doc.add(JsonPatchOperation('add', SystemFields.CREATED_DATE, '01-01-2018'))
# Set the bypass_rules parameter to True
client.create_workitem('Contoso', 'User Story', doc, bypass_rules=True)
client.delete_workitem(1)
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
for team in client.get_teams('project name')['value']:
print(str(team))
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
project_name = 'project name'
for team in client.get_teams(project_name)['value']:
for dev in client.get_team_members(project_name, team['id'])['value']:
print(dev['identity']['displayName']] + ': ' + dev['identity']['uniqueName'])
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
comments = client.get_comments_from_workitem('project', 73)
Comments inside a work item are indexed by comment id.
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
workitem_id = 73
comment_id = 8307896
comment = client.get_comment_from_workitem('project', workitem_id, comment_id)
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
comment = client.create_comment('project', 73, 'This is a test comment!')
from vstsclient.vstsclient import VstsClient
workitem_id = 73
comment_id = 8307896
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
client.delete_comment('project', workitem_id, comment_id)
Create a new field.
from vstsclient.vstsclient import VstsClient
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
name = 'New work item field'
ref_name = 'new.work.item.field'
field = client.create_field(
name, # Name
ref_name, # Reference name
'Contoso', # Project name
'Field description', # Field description
'string', # Field type: boolean, string, dateTime, integer, double, guid, html, identity, plainText, etc.
'workItem', # Field usage: none, tree, workItem, workItemLink or workItemTypeExtension
[{ # Supported operations
'referenceName': 'SupportedOperations.Equals',
'name': '='
}])
ref_name = 'new.workitem.field' # Name or reference name
prj_name = 'Contoso' # Project name
field = client.get_field(ref_name, prj_name)
ref_name = 'new.workitem.field' # Name or reference name
prj_name = 'Contoso' # Project name
client.delete_field(ref_name, prj_name)
# Specifying the team project is optional
query = "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.Title] = 'User Story A'"
result = client.query(query, 'Contoso')
for row in result.rows:
workitem_id = row['id']
workitem = client.get_workitem(id)
Note that the query returns a list of work item ids
You can obtain information about supported API versions of your server for each topic (git, wit, etc). Please see this Github issue from MicrosoftDocs/vsts-docs for detailed explanation about this version API.
client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
client.get_api_info('wit')
The call get_api_info()
returns an array of supported API versions. For example with work item comments accessed by revision (this API has):
{
'area': 'wit',
'id': '19335ae7-22f7-4308-93d8-261f9384b7cf',
'maxVersion': '5.0',
'minVersion': '3.0',
'releasedVersion': '0.0',
'resourceName': 'comments',
'resourceVersion': 2,
'routeTemplate': '{project}/_apis/{area}/workItems/{id}/comments/{revision}'
}