/vsts-client

A client library for Microsoft Azure DevOps (VSTS) and TFS written in Python

Primary LanguagePythonMIT LicenseMIT

Azure DevOps (VSTS) & TFS Client

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.

Installation

pip install vsts-client

Connecting to Azure DevOps

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>')

What about TFS?

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>')

Connecting from behind a proxy

client.set_proxy('proxy.contoso.com', 8080, '<username>', '<password>')

Team Projects

Get a list of team projects

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) 

Get a team project

from vstsclient.vstsclient import VstsClient

client  = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
project = client.get_project('Contoso')

Create a team project

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

Areas and Iterations

All work items have an area and an iteration field. The values that these fields can have are defined in the classification hierarchies.

Get a list of areas and iterations

Get the root area tree

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
areas  = client.get_areas('Contoso')

Get the area tree with 2 levels of children

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)

Get the root iteration tree

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
iterations = client.get_iterations('Contoso')

Get the iteration tree with 2 levels of children

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)

Get an area and iteration

Get an area

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
area = client.get_area('Contoso', 'Area 1')

Get an iteration

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
iteration = client.get_iteration('Contoso', 'Sprint 1')

Create an area and iteration

Create an area

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
area = client.create_area(
    'Contoso',  # Team project name
    'Area 1')   # Area name

Create an iteration

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

Work items

By IDs

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')

Get a work item

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
workitem = client.get_workitem(13)

Create a work item

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

Update work items

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)

Change work item type

Only supported on Azure DevOps (not on TFS).

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
client.change_workitem_type(13, 'Task')

Move a work item

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')

Add a tag

tags = ['Tag1', 'Tag2']
client.add_tags(13, tags)

Add a link

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')

Add an attachment

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')

Update work items bypassing rules

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, or System.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 and System.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)

Delete a work item

client.delete_workitem(1)

Access team and team members in a project

Get all teams in a project in a project

from vstsclient.vstsclient import VstsClient

client   = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
for team in client.get_teams('project name')['value']:
    print(str(team))

Get members of all teams in a project

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'])

Managing comments of work items

Get all comments of a work item

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
comments = client.get_comments_from_workitem('project', 73)

Get specific comment of a work item

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)

Add a comment of a work item

from vstsclient.vstsclient import VstsClient

client = VstsClient('dev.azure.com/<account>', '<personalaccesstoken>')
comment = client.create_comment('project', 73, 'This is a test comment!')

Removing a comment of a work item

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)

Fields

Create a field

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': '='
    }])

Get a field

ref_name = 'new.workitem.field' # Name or reference name
prj_name = 'Contoso'            # Project name

field = client.get_field(ref_name, prj_name)

Delete a field

ref_name = 'new.workitem.field' # Name or reference name
prj_name = 'Contoso'            # Project name

client.delete_field(ref_name, prj_name)

Work item query language (WIQL)

Run a query

# 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

Supported API version in Azure DevOps and TFS

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}'
}