Skip to content

Commit 93608f3

Browse files
authored
Merge pull request #377 from SynBioDex/364-downloadAttachment
Implement downloadAttachment
2 parents cc58e1b + d5bfe10 commit 93608f3

2 files changed

Lines changed: 87 additions & 4 deletions

File tree

sbol2/partshop.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,55 @@ def attachFile(self, top_level_uri, filepath):
344344
msg = msg.format(response.status_code)
345345
raise SBOLError(SBOLErrorCode.SBOL_ERROR_BAD_HTTP_REQUEST, msg)
346346

347+
def downloadAttachment(self, attachment_uri, filepath='.'):
348+
'''Download a file attachment from SynBioHub.
349+
:param attachment_uri: The URI of the file to download
350+
:param filepath: The local path or filename to which the file will be written
351+
Returns None if successful.
352+
353+
Raises EnvironmentError if the filepath is invalid
354+
355+
Raises SBOLError with code SBOL_ERROR_HTTP_UNAUTHORIZED if it
356+
there is an HTTP Unauthorized response.
357+
358+
Raises SBOLError with code SBOL_ERROR_BAD_HTTP_REQUEST on any
359+
other HTTP error. The actual status code is embedded in the
360+
string message.
361+
'''
362+
url = self._uri2url(attachment_uri)
363+
url = posixpath.join(url, 'download')
364+
filepath = os.path.expanduser(filepath)
365+
366+
# HTTP request headers
367+
headers = {
368+
'Accept': 'text/plain',
369+
'X-authorization': self.key
370+
}
371+
372+
# Issue GET request
373+
response = requests.get(url, headers=headers)
374+
if response.ok:
375+
filename = response.headers['Content-Disposition']
376+
filename = filename[22:-1] # remove extraneous text: attachment; filename="
377+
if os.path.isdir(filepath):
378+
filepath = posixpath.join(filepath, filename)
379+
with open(filepath, 'wb') as filehandle:
380+
filehandle.write(response.content)
381+
return
382+
if response.status_code == http.HTTPStatus.UNAUTHORIZED:
383+
# HTTP 401
384+
msg = 'You must login with valid credentials before downloading a file'
385+
raise SBOLError(SBOLErrorCode.SBOL_ERROR_HTTP_UNAUTHORIZED, msg)
386+
if response.status_code == http.HTTPStatus.NOT_FOUND:
387+
# HTTP 404
388+
msg = 'Unable to download. Attachment {} not found.'.format(attachment_uri)
389+
raise SBOLError(SBOLErrorCode.SBOL_ERROR_NOT_FOUND, msg)
390+
391+
# Not sure what went wrong
392+
msg = 'HTTP Error code {} trying to download file.'
393+
msg = msg.format(response.status_code)
394+
raise SBOLError(SBOLErrorCode.SBOL_ERROR_BAD_HTTP_REQUEST, msg)
395+
347396
def _make_search_item(self, item: dict) -> Identified:
348397
obj = Identified()
349398
obj.identity = item['uri']

test/test_partshop.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import unittest
3-
3+
import json
44
import requests
55

66
import sbol2 as sbol
@@ -213,6 +213,7 @@ def test_uri2url_spoofed(self):
213213

214214
@unittest.skipIf(password is None, "No password supplied")
215215
def test_attach_file(self):
216+
sbol2.Config.setOption('sbol_typed_uris', False)
216217
doc = sbol2.Document()
217218
doc.displayId = 'test_attachment'
218219
doc.name = 'test attachment'
@@ -222,11 +223,44 @@ def test_attach_file(self):
222223
sbh = sbol2.PartShop(TEST_RESOURCE)
223224
sbh.login(username, password)
224225
sbh.submit(doc, overwrite=1)
225-
md_uri = '{}/user/{}/{}/{}/{}'.format(sbh.getURL(), sbh.getUser(),
226-
doc.displayId, md.displayId,
227-
md.version)
226+
md_uri = '{}/user/{}/{}/{}/{}'.format(sbh.getURL(), sbh.getUser(), doc.displayId,
227+
md.displayId, md.version)
228228
sbh.attachFile(md_uri, CRISPR_LOCATION)
229229

230+
# SBH will autogenerate an Attachment object, so now we query to find out
231+
# what the URI of that Attachment object is so we can download it
232+
query = '''
233+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
234+
PREFIX dcterms: <http://purl.org/dc/terms/>
235+
PREFIX dc: <http://purl.org/dc/elements/1.1/>
236+
PREFIX sbh: <http://wiki.synbiohub.org/wiki/Terms/synbiohub#>
237+
PREFIX prov: <http://www.w3.org/ns/prov#>
238+
PREFIX sbol: <http://sbols.org/v2#>
239+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
240+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
241+
PREFIX purl: <http://purl.obolibrary.org/obo/>
242+
SELECT DISTINCT ?attachment_uri
243+
WHERE {{
244+
<{}> sbol:attachment ?attachment_uri
245+
}}'''.format(md_uri)
246+
response = sbh.sparqlQuery(query)
247+
content = json.loads(response.content)
248+
attachment_uri = content['results']['bindings'][0]['attachment_uri']['value']
249+
# Save to default filename
250+
sbh.downloadAttachment(attachment_uri)
251+
self.assertTrue(os.path.exists('./crispr_example.xml'))
252+
os.remove('crispr_example.xml')
253+
# Save to new file name
254+
sbh.downloadAttachment(attachment_uri, 'foo.xml')
255+
self.assertTrue(os.path.exists('./foo.xml'))
256+
os.remove('foo.xml')
257+
# Confirm error handling
258+
sbh.remove(attachment_uri)
259+
with self.assertRaises(sbol2.SBOLError) as cm:
260+
sbh.downloadAttachment(attachment_uri)
261+
e = cm.exception
262+
self.assertEqual(e.error_code(), sbol2.SBOLErrorCode.SBOL_ERROR_NOT_FOUND)
263+
230264
def test_search_general(self):
231265
sbh = sbol2.PartShop(TEST_RESOURCE_MAIN)
232266
# sbh.login(username, password)

0 commit comments

Comments
 (0)