使用Python中的Rally REST API复制缺陷的首选方法

时间:2013-01-08 17:38:45

标签: python rally pyral

我希望能够查询Rally中的现有缺陷,然后复制该缺陷,只更改几个字段,同时保留所有附件。有一个简单的方法吗?我尝试调用rally.create并传递现有的缺陷对象,但无法将所有成员序列化为JSON。最终,如果扩展pyral以包含这种功能,那就太好了。

相反,我编写了一些代码来复制现有缺陷的每个python-native属性,然后将.ref用于其他所有内容。它看起来运作得很好。我已经利用Mark W的代码来复制附件,这也很有用。另一个令人沮丧的是复制迭代不起作用。当我在Iteration属性上调用.ref时,我得到了这个:

>>> s
<pyral.entity.Defect object at 0x029A74F0>
>>> s.Iteration
<pyral.entity.Iteration object at 0x029A7710>
>>> s.Iteration.ref
No classFor item for |UserIterationCapacity|
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\python27\lib\site-packages\pyral\entity.py", line 119, in __getattr__
    hydrateAnInstance(self._context, item, existingInstance=self)
  File "c:\python27\lib\site-packages\pyral\restapi.py", line 77, in hydrateAnInstance
    return hydrator.hydrateInstance(item, existingInstance=existingInstance)
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 62, in hydrateInstance
    self._setAppropriateAttrValueForType(instance, attrName, attrValue, 1)
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 128, in _setAppropriateAttrValueForType
    elements = [self._unravel(element) for element in attrValue]
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 162, in _unravel
    return self._basicInstance(thing)
  File "c:\python27\lib\site-packages\pyral\hydrate.py", line 110, in _basicInstance
    raise KeyError(itemType)
KeyError: u'UserIterationCapacity'
>>>

这看起来像是拉力赛的问题,还是我们的项目管理员可能导致的自定义字段问题?我能够通过构建来自oid的ref来解决它:

newArtifact["Iteration"] = { "_ref": "iteration/" + currentArtifact.Iteration.oid }

但这对我来说感觉很糟糕。

2 个答案:

答案 0 :(得分:0)

查看Python的答案(有2个答案,另一个是针对Ruby的)来回答这个问题:

Rally APIs: How to copy Test Folder and member Test Cases

答案包含一个复制测试用例和附件的python脚本。尽管该脚本适用于测试用例,但逻辑应该很容易适应缺陷,因为操作基本上是相同的 - 只有字段属性会有所不同。该脚本使用附件,标签等进行复制,几乎就是整个工件。

答案 1 :(得分:0)

最终解决方案,包括Mark W的复制附件代码

def getDataCopy( data ):

    """ Given a piece of data, figure out how to copy it.  If it's a native python type
        like a string or numeric, just return the value.  If it's a rally object, return
        the ref to it.  If it's a list, iterate and call ourself recursively for the
        list members. """

    if isinstance( data, types.ListType ):
        copyData = []
        for entry in data:
            copyData.append( getDataCopy(entry) )

    elif hasattr( data, "ref" ):
        copyData = { "_ref": data.ref }

    else:
        copyData = data

    return copyData

def getArtifactCopy( artifact ):

    """ Build a dictionary based on the values in the specified artifact.  This dictionary
        can then be passed to a rallyConn.put() call to actually create the new entry in
        Rally.  Attachments and Tasks must be copied seperately, since they require creation
        of additional artifacts """

    newArtifact = {}

    for attrName in artifact.attributes():

        # Skip the attributes that we can't or shouldn't handle directly
        if attrName.startswith("_") or attrName == "oid" or attrName == "Iteration" or attrName == "Attachments":
            continue

        attrValue = getattr( artifact, attrName )
        newArtifact[attrName] = getDataCopy( attrValue )

    if getattr( artifact, "Iteration", None ) != None:
        newArtifact["Iteration"] = { "_ref": "iteration/" + artifact.Iteration.oid }

    return newArtifact

def copyAttachments( rallyConn, oldArtifact, newArtifact ):

    """ For each attachment in the old artifact, create new attachments and attach them to the new artifact"""

    # Copy Attachments
    source_attachments = rallyConn.getAttachments(oldArtifact)

    for source_attachment in source_attachments:

        # First copy the content
        source_attachment_content = source_attachment.Content
        target_attachment_content_fields = { "Content": base64.encodestring(source_attachment_content) }

        try:
            target_attachment_content = rallyConn.put( 'AttachmentContent', target_attachment_content_fields )
            print "\t===> Copied AttachmentContent: %s" % target_attachment_content.ref
        except pyral.RallyRESTAPIError, details:
            sys.stderr.write('ERROR: %s \n' % details)
            sys.exit(2)

        # Next copy the attachment object
        target_attachment_fields = {
            "Name": source_attachment.Name,
            "Description": source_attachment.Description,
            "Content": target_attachment_content.ref,
            "ContentType": source_attachment.ContentType,
            "Size": source_attachment.Size,
            "User": source_attachment.User.ref
        }

        # Attach it to the new artifact
        target_attachment_fields["Artifact"] = newArtifact.ref

        try:
            target_attachment = rallyConn.put( source_attachment._type, target_attachment_fields)
            print "\t===> Copied Attachment: '%s'" % target_attachment.Name
        except pyral.RallyRESTAPIError, details:
            sys.stderr.write('ERROR: %s \n' % details)
            sys.exit(2)

def copyTasks( rallyConn, oldArtifact, newArtifact ):

    """ Iterate over the old artifacts tasks and create new ones, attaching them to the new artifact """

    for currentTask in oldArtifact.Tasks:

        newTask = getArtifactCopy( currentTask )

        # Assign the new task to the new artifact
        newTask["WorkProduct"] = newArtifact.ref

        # Push the new task into rally
        newTaskObj = rallyConn.put( currentTask._type, newTask )

        # Copy any attachments the task had
        copyAttachments( rallyConn, currentTask, newTaskObj )

def copyDefect( rallyConn, currentDefect, addlUpdates = {} ):

    """ Copy a defect including its attachments and tasks.  Add the new defect as a
        duplicate to the original """

    newArtifact = getArtifactCopy( currentDefect )

    # Add the current defect as a duplicate for the new one
    newArtifact["Duplicates"].append( { "_ref": currentDefect.ref } )

    # Copy in any updates that might be needed
    for (attrName, attrValue) in addlUpdates.items():
        newArtifact[attrName] = attrValue

    print "Copying %s: %s..." % (currentDefect.Project.Name, currentDefect.FormattedID),
    newDefect = rallyConn.create( currentDefect._type, newArtifact )

    print "done, new item", newDefect.FormattedID

    print "\tCopying attachments"
    copyAttachments( rallyConn, currentDefect, newDefect )

    print "\tCopying tasks"
    copyTasks( rallyConn, currentDefect, newDefect )