2

I have a fairly simple use-case but i'm not understanding the error message i'm receiving.

I'm using the requests and pyral modules, pyral (http://pyral.readthedocs.io/en/latest/interface.html#) is really just a wrapper for Rally's Restful api. My goal is to get a file (attachment) from a Rally (a CA product) UserStory and store it to a local file system.

For context, here is my environment setup (authenticate to Rally and create an object). I've obviously removed authentication information.

from pyral import Rally, rallyWorkset

options = [arg for arg in sys.argv[1:] if arg.startswith('--')]
args = [arg for arg in sys.argv[1:] if arg not in options]
server, user, password, apikey, workspace, project = rallyWorkset(options)
rally = Rally(server='rally1.rallydev.com', 
user='**********', password='***********', 
          apikey="**************",
          workspace='**************', project='**************',
          server_ping=False)

After that I get a response object for just one user story (see the query for US845), i do this just to simplify the problem.

r = rally.get('UserStory', fetch = True, projectScopeDown=True, query = 'FormattedID = US845')

and then I use the built-in iterator to get the user story from the RallyRESTResponse object.

us = r.next()

from there it feels like I should be able to easily use the getAttachment() method that accepts a artifact (us) and filename (name of an attachment). I'm able to use getAttachmentNames(us) to return a list of attachment names. The issue arrises when i try something like

attachment_names = rally.getAttachmentNames(us) #get attachments for this UserStory
attachment_file = rally.getAttachment(us, attachment_names[0]) #Try to get the first attachment 

returns an error like this

Traceback (most recent call last):

File "<ipython-input-81-a4a342a59c5a>", line 1, in <module>
attachment_file = rally.getAttachment(us, attachment_names[0])

File "C:\Miniconda3\lib\site-packages\pyral\restapi.py", line 1700, in getAttachment
att.Content = base64.decodebytes(att_content.Content)  # maybe further txfm to Unicode ?

File "C:\Miniconda3\lib\base64.py", line 552, in decodebytes
_input_type_check(s)

File "C:\Miniconda3\lib\base64.py", line 520, in _input_type_check
raise TypeError(msg) from err

TypeError: expected bytes-like object, not str

I receive a similar error if i try to use

test_obj = rally.getAttachments(us)

Which returns an error like this:

Traceback (most recent call last):

File "<ipython-input-82-06a8cd525177>", line 1, in <module>
rally.getAttachments(us)

File "C:\Miniconda3\lib\site-packages\pyral\restapi.py", line 1721, in getAttachments
attachments = [self.getAttachment(artifact, attachment_name) for attachment_name in attachment_names]

File "C:\Miniconda3\lib\site-packages\pyral\restapi.py", line 1721, in <listcomp>
attachments = [self.getAttachment(artifact, attachment_name) for attachment_name in attachment_names]

File "C:\Miniconda3\lib\site-packages\pyral\restapi.py", line 1700, in getAttachment
att.Content = base64.decodebytes(att_content.Content)  # maybe further txfm to Unicode ?

File "C:\Miniconda3\lib\base64.py", line 552, in decodebytes
_input_type_check(s)

File "C:\Miniconda3\lib\base64.py", line 520, in _input_type_check
raise TypeError(msg) from err

TypeError: expected bytes-like object, not str

It seems that i'm fundamentally misunderstanding the parameters that this method requires? Has anyone been able to do this successfully before? For what it's worth i have no issues using the addAttachment() method with a workflow similar to the above. I've tried converting the filename (string) with the bytes() method to utf-8 but that didn't help.

I've also looked at this example in the pyral source, but i receive exactly the same error when trying to execute that.

https://github.com/klehman-rally/pyral/blob/master/examples/get_attachments.py

sc305495
  • 249
  • 3
  • 11

2 Answers2

2

It looks like the issue in restapi.py script - there is no decodebytes method in base64 library:

att.Content = base64.decodebytes(att_content.Content)

All available methods are described at: RFC 3548: Base16, Base32, Base64 Data Encodings So, workaround is to replace decodebytes by base64.b64decode in restapi.py. At least, it works me.

E.g. location at Mac OS X:

/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pyral/restapi.py
user2738882
  • 1,161
  • 1
  • 20
  • 38
  • I hadn't thought the issue could be with the module itself and not me :). Thank you for the validation! I've submitted an issue on GitHub. – sc305495 Apr 23 '18 at 20:35
  • Works for me too to use `base64.b64decode` and on both Python 2.7 and Python 3.6 (better than my solution that had to check Python version). – Jean-Francois T. Apr 24 '18 at 13:34
  • @sc305495 FYI, I have created a fork of Pyral with a correction of that bug (https://github.com/jfthuong/RallyRestToolkitForPython). – Jean-Francois T. Apr 24 '18 at 15:28
  • @Jean-FrancoisT. thanks, big help! Do you have any advice on extracting an excel document (or any document really) and storing it on a file system? I've tried to use pickle.dump(.Content, , pickle.HIGHEST_PROTOCOL), but perhaps that's the wrong approach? – sc305495 Apr 25 '18 at 14:22
  • Never mind :). A simple output = open('test.xlsx', 'wb) output.write(attachment_file.Content) did the trick. Thanks again for the help! – sc305495 Apr 25 '18 at 15:42
1

I have used the below code to get all attachment since getAttachments is not working as expected. it will create a file in the current dir with the same name.

import sys
import string
import base64
from pyral import rallyWorkset, Rally,RallyRESTResponse

rally = Rally(server, user=USER_NAME, password=PASSWORD, workspace=workspace, project=project)
criterion = 'FormattedID = US57844'
response = rally.get('HierarchicalRequirement',  query=criterion, order="FormattedID",pagesize=200, limit=400, projectScopeDown=True)

artifact = response.next()
context, augments = rally.contextHelper.identifyContext()
for att in artifact.Attachments:
    resp = rally._getResourceByOID(context, 'AttachmentContent', att.Content.oid, project=None)
    if resp.status_code not in [200, 201, 202]:
        break
    res = RallyRESTResponse(rally.session, context, "AttachmentContent.x", resp, "full", 1)
    if res.errors or res.resultCount != 1:
        print("breaking the for loop")
    att_content = res.next()
    cont = att_content.Content
    x = base64.b64decode(cont)
    output = open(att.Name, 'wb') 
    output.write(x)
bhaskar
  • 11
  • 2