Not sure if any Python developers have used Microsoft's Team Foundation Server, it includes an application called Product Studio for managing bugs in your companies software. My job uses it for escalations to server teams and such. Recently, I found out about the SDK, which uses the Windows COM system, and Python has a module for this, win32com. I wrote this very handle class to query Product Studio for bugs, and also create new bugs. The class is very basic, but gets the job done.
import win32com.client
class ProductStudio(object):
def __init__(self, product='Service Delivery Escalation'):
self.psdir = win32com.client.Dispatch("ProductStudio.Directory")
self.psdir.Connect("domain.com")
self.prod = self.psdir.GetProductByName(product)
self.ds = self.prod.Connect()
def query(self, psq):
q = win32com.client.Dispatch("ProductStudio.Query")
q.CountOnly = False
q.DatastoreItemType = -100
lst = win32com.client.Dispatch("ProductStudio.DataStoreItemList")
lst.Query = q
lst.Datastore = self.ds
q.SelectionCriteria = psq
fields = self.ds.FieldDefinitions
q.QueryFields.Clear()
q.QueryFields.Add(fields("Title"))
q.QuerySortFields.Add(fields("ID"), 0)
lst.Execute()
items = []
for item in lst.DatastoreItems:
items.append({'bugid':item.Fields("ID")(),
'title':item.Fields("Title")(),
'opened_by':item.Fields("Opened By")(),
'status':item.Fields("Status")(),
'created_on':item.Fields("Opened Date")()})
return items
def _equals(self, equals):
psq = ""
for column in equals:
psq += "<Expression Column='%s' Operator='equals'>" % column
psq += "<String>%s</String>" % equals[column]
psq += "</Expression>"
return psq
def make_query(self, equals={}):
psq = "<Query>"
if not isinstance(equals, dict):
psq += "<Group GroupOperator='or'>"
for row in equals:
psq += self._equals(row)
psq += "</Group>"
else:
psq += self._equals(equals)
psq += "</Query>"
return psq
class Bug(object):
def __init__(self, datastore, path):
self.lst = win32com.client.Dispatch("ProductStudio.DataStoreItemList")
self.lst.Datastore = datastore
self.lst.CreateBlank(-100)
self.item = self.lst.DatastoreItems.Add()
self.fields = self.item.Fields
treeid = self._get_treeid(datastore.RootNode, path)
self.fields["TreeID"].Value = treeid
def __setitem__(self, key, value):
self.fields[key].Value = value
def __getitem__(self, key):
return self.fields[key].Value
def is_valid(self):
self.errors = []
for field in self.fields:
if field.Validity != 0:
self.errors.append(field.Name)
if self.errors != []:
return False
return True
def save(self):
if self.is_valid():
self.item.Save()
return self.fields["ID"].Value
return 0
def _get_treeid(self, node, path):
if len(path) == 0:
return node.ID
name = path.split("\\")[0]
for subnode in node.Nodes:
if subnode.Name == name:
try:
subsub = path.split("\\", 1)[1]
except IndexError:
return subnode.ID
return self._get_treeid(subnode, subsub)
I originally created the querying code for a reporting tool I am working on, which is why it returns a specific set of fields from the database. The bug data model came next, and I sub-class this model to set up a specific template for a bug report. Hopefully someone else finds this helpful.
