diff --git a/.gitignore b/.gitignore index 1e0f5da..d31d26f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ build eggs doc_build parts -bin var sdist /scripts +data/BC-12_S12_L001_R2_001.fastq.gz diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7f5dca4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: python +python: + - "2.7" + - "3.5" +install: "cd src && python setup.py install && cd .." +script: ./runtests.sh diff --git a/README.md b/README.md index 07f38c6..07f1b7f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -INTRODUCTION +[![Build Status][travis-image]](https://travis-ci.org/dtenenba/basespace-python-sdk) + +[travis-image]: https://img.shields.io/travis/dtenenba/basespace-python-sdk.svg?style=flat&branch=develop + +INTRODUCTION ========================================= -BaseSpacePy is a Python based SDK to be used in the development of Apps and scripts for working with Illumina's BaseSpace cloud-computing solution for next-gen sequencing data analysis. +BaseSpacePy is a Python based SDK to be used in the development of Apps and scripts for working with Illumina's BaseSpace cloud-computing solution for next-gen sequencing data analysis. The primary purpose of the SDK is to provide an easy-to-use Python environment enabling developers to authenticate a user, retrieve data, and upload data/results from their own analysis to BaseSpace. @@ -20,7 +24,7 @@ Mauricio Varea REQUIREMENTS ========================================= -Python 2.6 with the packages 'pycurl', and 'python-dateutil' installed. You can install these on Ubuntu with 'apt-get install python-pycurl' and 'apt-get install python-dateutil'. +Python 2.6 with the package 'python-dateutil' installed. You can install these on Ubuntu with 'apt-get install python-dateutil'. INSTALL @@ -39,7 +43,7 @@ If you do not have root access, you may use the --prefix to specify the install python setup.py install --prefix=/folder/in/my/pythonpath -For more install options type: +For more install options type: python setup.py --help @@ -53,7 +57,7 @@ or add it to the PYTHONPATH at the top of your Python scripts using BaseSpacePy: sys.path.append('/my/path/basespace-python-sdk/src') import BaseSpacePy -To test that everything is working as expected, launch a Python prompt and try importing 'BaseSpacePy': +To test that everything is working as expected, launch a Python prompt and try importing 'BaseSpacePy': mkallberg@ubuntu:~/$ python >>> import BaseSpacePy @@ -115,7 +119,7 @@ Update to support changes in BaseSpace REST specification version v1pre3. Specif v 0.1 ----------------------------------------- - + Initial release of BaseSpacePy COPYING / LICENSE diff --git a/doc/_update_doc/Getting Started.txt b/doc/_update_doc/Getting Started.txt index d71eb83..654e28f 100644 --- a/doc/_update_doc/Getting Started.txt +++ b/doc/_update_doc/Getting Started.txt @@ -25,7 +25,7 @@ Version 0.1 of ``BaseSpacePy`` can be checked out here: Setup ################### -*Requirements:* Python 2.6 with the packages 'urllib2', 'pycurl', 'multiprocessing' and 'shutil' available. +*Requirements:* Python 2.6 with the packages 'urllib2', 'multiprocessing' and 'shutil' available. The multi-part file upload will currently only run on a unix setup. diff --git a/doc/_update_doc/conf.py b/doc/_update_doc/conf.py index c9f4e21..d75b995 100644 --- a/doc/_update_doc/conf.py +++ b/doc/_update_doc/conf.py @@ -40,8 +40,8 @@ master_doc = 'index' # General information about the project. -project = u'BaseSpacePy' -copyright = u'2014, Illumina' +project = 'BaseSpacePy' +copyright = '2014, Illumina' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -183,8 +183,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'BaseSpacePy.tex', u'BaseSpacePy Documentation', - u'Illumina', 'manual'), + ('index', 'BaseSpacePy.tex', 'BaseSpacePy Documentation', + 'Illumina', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -213,8 +213,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'basespacepy', u'BaseSpacePy Documentation', - [u'Illumina'], 1) + ('index', 'basespacepy', 'BaseSpacePy Documentation', + ['Illumina'], 1) ] # If true, show URL addresses after external links. @@ -227,8 +227,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'BaseSpacePy', u'BaseSpacePy Documentation', - u'Illumina', 'BaseSpacePy', 'One line description of project.', + ('index', 'BaseSpacePy', 'BaseSpacePy Documentation', + 'Illumina', 'BaseSpacePy', 'One line description of project.', 'Miscellaneous'), ] diff --git a/doc/html/Getting Started.html b/doc/html/Getting Started.html index 522ba9a..69388ca 100644 --- a/doc/html/Getting Started.html +++ b/doc/html/Getting Started.html @@ -67,7 +67,7 @@
Requirements: Python 2.6 with the packages ‘urllib2’, ‘pycurl’, ‘multiprocessing’ and ‘shutil’ available.
+Requirements: Python 2.6 with the packages ‘urllib2’, ‘multiprocessing’ and ‘shutil’ available.
The multi-part file upload will currently only run on a unix setup.
To install ‘BaseSpacePy’ run the ‘setup.py’ script in the src directory (for a global install you will need to run this command with root privileges):
cd basespace-python-sdk/src
diff --git a/doc/html/_modules/BaseSpacePy/api/BaseSpaceAPI.html b/doc/html/_modules/BaseSpacePy/api/BaseSpaceAPI.html
index e2006c6..cbf5f83 100644
--- a/doc/html/_modules/BaseSpacePy/api/BaseSpaceAPI.html
+++ b/doc/html/_modules/BaseSpacePy/api/BaseSpaceAPI.html
@@ -50,7 +50,6 @@ Source code for BaseSpacePy.api.BaseSpaceAPI
import urllib2
import shutil
import urllib
-import pycurl
import httplib
import cStringIO
import json
@@ -252,9 +251,9 @@ Source code for BaseSpacePy.api.BaseSpaceAPI
resourcePath = self.apiClient.apiServer + '/appsessions/{AppSessionId}'
resourcePath = resourcePath.replace('{AppSessionId}', Id)
response = cStringIO.StringIO()
- c = pycurl.Curl()
- c.setopt(pycurl.URL, resourcePath)
- c.setopt(pycurl.USERPWD, self.key + ":" + self.secret)
+ c = .Curl()
+ c.setopt(.URL, resourcePath)
+ c.setopt(.USERPWD, self.key + ":" + self.secret)
c.setopt(c.WRITEFUNCTION, response.write)
c.perform()
c.close()
diff --git a/doc/html/_sources/Getting Started.txt b/doc/html/_sources/Getting Started.txt
index d71eb83..654e28f 100644
--- a/doc/html/_sources/Getting Started.txt
+++ b/doc/html/_sources/Getting Started.txt
@@ -25,7 +25,7 @@ Version 0.1 of ``BaseSpacePy`` can be checked out here:
Setup
###################
-*Requirements:* Python 2.6 with the packages 'urllib2', 'pycurl', 'multiprocessing' and 'shutil' available.
+*Requirements:* Python 2.6 with the packages 'urllib2', 'multiprocessing' and 'shutil' available.
The multi-part file upload will currently only run on a unix setup.
diff --git a/doc/html/searchindex.js b/doc/html/searchindex.js
index d240da6..b6533b2 100644
--- a/doc/html/searchindex.js
+++ b/doc/html/searchindex.js
@@ -1 +1 @@
-Search.setIndex({objects:{"BaseSpacePy.model.File.File":{getIntervalCoverage:[2,0,1,""],isValidFileOption:[2,0,1,""],isInit:[2,0,1,""],downloadFile:[2,0,1,""],getVariantMeta:[2,0,1,""],getCoverageMeta:[2,0,1,""],getFileUrl:[2,0,1,""],filterVariant:[2,0,1,""],getFileS3metadata:[2,0,1,""]},"BaseSpacePy.model.QueryParameters":{QueryParameters:[2,1,1,""]},"BaseSpacePy.model.Run.Run":{isInit:[2,0,1,""],getFiles:[2,0,1,""],getSamples:[2,0,1,""],getAccessStr:[2,0,1,""]},"BaseSpacePy.api.BaseSpaceAPI.BaseSpaceAPI":{getFileById:[2,0,1,""],getAppResultFilesById:[2,0,1,""],getRunFilesById:[2,0,1,""],getUserById:[2,0,1,""],getProjectById:[2,0,1,""],filterVariantSet:[2,0,1,""],getIntervalCoverage:[2,0,1,""],getAppSession:[2,0,1,""],getAppSessionById:[2,0,1,""],getAccessibleRunsByUser:[2,0,1,""],getGenomeById:[2,0,1,""],setAppSessionState:[2,0,1,""],getWebVerificationCode:[2,0,1,""],createAppResult:[2,0,1,""],getSamplePropertiesById:[2,0,1,""],getAppSessionPropertiesById:[2,0,1,""],obtainAccessToken:[2,0,1,""],getAppSessionPropertyByName:[2,0,1,""],multipartFileDownload:[2,0,1,""],multipartFileUpload:[2,0,1,""],updatePrivileges:[2,0,1,""],getAppResultsByProject:[2,0,1,""],getAppResultFiles:[2,0,1,""],getProjectPropertiesById:[2,0,1,""],getVariantMetadata:[2,0,1,""],getRunById:[2,0,1,""],createProject:[2,0,1,""],getFilePropertiesById:[2,0,1,""],getAccess:[2,0,1,""],getFilesBySample:[2,0,1,""],getAppSessionInputsById:[2,0,1,""],getVerificationCode:[2,0,1,""],getAvailableGenomes:[2,0,1,""],getSampleById:[2,0,1,""],appResultFileUpload:[2,0,1,""],fileS3metadata:[2,0,1,""],fileDownload:[2,0,1,""],getSamplesByProject:[2,0,1,""],getCoverageMetaInfo:[2,0,1,""],getProjectByUser:[2,0,1,""],getSampleFilesById:[2,0,1,""],getAppResultById:[2,0,1,""],getRunSamplesById:[2,0,1,""],fileUrl:[2,0,1,""],getAppResultPropertiesById:[2,0,1,""],getRunPropertiesById:[2,0,1,""]},"BaseSpacePy.model.AppSession":{AppSession:[2,1,1,""]},"BaseSpacePy.model.QueryParameters.QueryParameters":{validate:[2,0,1,""]},"BaseSpacePy.model.Project":{Project:[2,1,1,""]},"BaseSpacePy.model.Sample":{Sample:[2,1,1,""]},"BaseSpacePy.model.AppResult.AppResult":{getFiles:[2,0,1,""],uploadFile:[2,0,1,""],isInit:[2,0,1,""],getReferencedSamples:[2,0,1,""],getAccessStr:[2,0,1,""],getReferencedSamplesIds:[2,0,1,""]},"BaseSpacePy.model.Run":{Run:[2,1,1,""]},"BaseSpacePy.api.BaseSpaceAPI":{BaseSpaceAPI:[2,1,1,""]},"BaseSpacePy.model.AppResult":{AppResult:[2,1,1,""]},"BaseSpacePy.model.Sample.Sample":{isInit:[2,0,1,""],getFiles:[2,0,1,""],getAccessStr:[2,0,1,""],getReferencedAppResults:[2,0,1,""]},"BaseSpacePy.model.Project.Project":{isInit:[2,0,1,""],getAppResults:[2,0,1,""],getAccessStr:[2,0,1,""],createAppResult:[2,0,1,""],getSamples:[2,0,1,""]},"BaseSpacePy.model.File":{File:[2,1,1,""]}},terms:{all:[0,2],code:[0,2],queri:[0,3,2],global:[0,3],getfilesbysampl:2,nwe:0,prefix:0,sleep:0,follow:0,getproject:0,depend:2,basespacepy_vx:0,getrunsamplesbyid:2,texliv:1,send:2,hrefcoverag:2,granular:2,present:2,sourc:2,string:[0,2],accesstoken:[0,2],fals:2,account:0,getfilepropertiesbyid:2,myprojects2:0,veri:[0,2],testfile2:0,brows:[0,3,2],getprojectbyid:[0,2],getappresultfilesbyid:2,contenttyp:2,level:2,list:[0,2],upload:[0,3,2],"try":0,item:0,getgenomebyid:[0,2],verif:[0,2],small:2,refer:2,round:2,dir:0,pleas:0,work:0,second:0,pass:2,download:[0,2],further:[0,2],cat:2,append:0,even:0,index:3,filedownload:2,compar:0,neg:2,section:0,abl:0,hrefvari:2,current:[0,2],delet:1,version:[0,1,2],"new":[0,1,2],method:[0,1,2],metadata:2,abov:0,gener:[0,1,2],here:0,shouldn:2,varmeta:0,let:0,ubuntu:0,basespaceauth:0,path:[0,2],getappsessioninputsbyid:2,sinc:1,valu:[0,2],search:3,queur:0,larger:2,prior:0,isinit:2,tauru:0,action:0,implement:2,chrchr2:0,getaccesstoken:0,privilig:0,extra:1,app:[0,2],apt:1,deprec:2,unix:[0,2],api:[0,3,2],getappsessionbyid:2,instal:[0,1],txt:[0,1],"2x26":0,cloud:0,from:[0,2],rattu:0,commun:2,visit:0,two:0,coverag:[0,2],next:0,websit:0,multipartfiledownload:2,call:[0,2],recommend:1,scope:[0,2],type:[0,2],nrun:0,more:[0,2],sort:[0,2],oauthexcept:2,desir:2,relat:[0,2],notic:0,granttyp:2,known:2,actual:2,hiseq:0,partsiz:2,must:[0,2],none:2,retriev:[0,2],pycurl:0,local:2,setup:[0,3],launch:0,getlaunchtyp:0,getfil:[0,2],getsamplepropertiesbyid:2,apporpri:0,endor:0,purpos:0,root:[0,2],norvegicu:0,nearest:2,prompt:0,stream:2,give:0,process:[0,2],accept:2,abort:2,want:0,sought:0,unknownparameterexcept:2,getrun:0,end:[0,2],applaunch:0,anoth:0,write:[0,2],how:0,regist:0,updat:[0,1,2],files3metadata:2,referenc:2,max:[0,2],clone:0,timedout:2,variant:[0,2],befor:0,mai:[0,1,2],associ:[0,2],parallel:2,demonstr:0,getfilebyid:[0,2],"short":0,attempt:2,chr2:[0,2],correspond:2,element:2,inform:[0,2],environ:0,thaliana:0,morten:0,varianthead:[0,2],mygenom:0,authorization_cod:2,help:[0,1],over:2,appsessionid:2,through:[0,2],coli:0,paramet:[0,2],alten:0,binari:2,getappresultfilebyid:2,getwebverificationcod:2,window:2,singleproject:0,pythonpath:0,local_dir:2,sapien:0,main:2,non:2,"return":2,thei:2,s_g1_l001_r2_001:0,python:[0,1],auto:1,cover:0,initi:[0,2],mybam:0,mybasespaceapi:0,now:0,introduct:[0,3],fontx:1,multiprocess:0,name:[0,2],edit:1,authent:[0,2],easili:0,token:[0,3,2],mode:2,timeout:2,genom:[0,2],debug:2,fulli:2,mean:[0,2],status:2,getreferencedappresult:2,chunk:2,hard:0,procedur:0,meta:0,expect:0,our:0,special:0,out:0,variabl:2,vcf:[0,3,2],newli:[0,1,2],getreferencedsamplesid:2,querypar:2,content:[1,3,2],uploadfil:[0,2],getprojectbyus:[0,2],etag:2,print:0,model:[3,2],after:[0,2],insid:2,situat:2,plex:0,triggerobj:0,base:[0,1,2],dictionari:2,needsattent:2,org:1,"byte":2,md5:2,thread:2,doctre:1,musculu:0,filter:[0,2],place:[0,2],isn:2,nthese:0,first:0,rang:2,directli:0,onc:[0,2],propertylist:2,number:[0,2],yourself:0,restrict:2,alreadi:[0,2],done:0,triggertyp:0,miss:2,primari:0,getfiles3metadata:2,size:2,createbspath:2,given:2,script:0,data:[0,3,2],top:0,system:2,store:2,option:[0,2],urllib2:0,specifi:[0,2],getappsesss:2,github:0,accompani:0,staphylococcu:0,than:2,endpo:2,downloadfil:[0,2],instanc:[0,2],provid:[0,2],remov:1,tree:[0,3],project:[0,3,2],str:0,posit:[0,2],multipart:2,comput:0,ana:0,fastq:0,filtervariantset:2,argument:2,client_kei:0,myproject:0,packag:0,bacillu:0,manner:0,have:[0,1,2],tabl:3,need:[0,1,2],getfileurl:2,illegalparameterexcept:2,amplicon:0,startpo:2,getrunbyid:2,getintervalcoverag:[0,2],client:[0,2],note:[0,2],also:0,chromosom:[0,2],exampl:[0,2],take:0,indic:3,singl:2,sure:[0,1],modelnotinitializedexcept:2,test:[0,2],object:[0,2],getverificationcod:[0,2],fileurl:2,"class":[0,1,2],latex:1,getsampl:[0,2],url:2,doc:1,request:[0,3,2],obtainaccesstoken:2,v1pre2:0,part:[0,2],getappresultbyid:2,getrunpropertiesbyid:2,phix:0,getrunfilesbyid:2,show:0,text:[0,2],filetyp:2,session:0,saccharomyc:0,permiss:0,redirecturl:2,cereu:0,redirect:2,access:[0,3,2],onli:[0,2],locat:0,createappresult:2,illumina:0,multivaluepropertyappresultslist:2,getappsess:2,transact:0,solut:0,state:[0,2],haven:0,dict:2,analyz:0,folder:[0,2],analys:0,get:[0,1,2,3],familiar:0,stop:2,getsamplebyid:2,chrom:2,gen:0,requir:[0,2],accesstyp:2,enabl:0,undefinedparameterexcept:2,acut:2,remot:2,allgenom:0,gran:0,privileg:0,grab:0,bam:[0,3,2],basespaceapi:[0,2],summari:[0,2],set:2,see:[0,1],result:[0,3,2],respons:2,fail:2,subject:0,statu:[0,2],getappsessionpropertybynam:2,getaccessiblerunsbyus:2,appresult:[3,2],favor:2,written:1,between:0,"import":0,attribut:2,altern:0,kei:[0,2],buckets:0,filtervari:[0,2],popul:2,verification_with_code_uri:0,last:0,cov:[0,2],samplecount:0,region:2,equal:2,createproject:2,etc:2,redirect_uri:2,pdf:1,com:0,getcoveragemeta:[0,2],nsome:0,simpli:0,coveragemetadata:2,can:[0,2],instanti:0,clientsecret:2,applicationact:0,header:2,empti:2,suppli:0,getappresultpropertiesbyid:2,assum:0,devic:[0,2],due:2,been:[0,2],getaccessstr:[0,2],secret:[0,2],trigger:[0,3],rhodobact:0,interest:2,basic:0,addit:0,clientkei:2,getprojectpropertiesbyid:2,getsamplesbyproject:2,getcoveragemetainfo:2,coordin:2,cerevisia:0,repres:2,those:2,"case":2,multi:[0,2],look:0,access_token:0,plain:[0,2],align:2,properti:2,basespac:[0,2],coveragemeta:0,"while":2,na18507:0,myvcf:0,error:2,getappresultfil:2,setappsessionst:2,howev:2,loop:0,getavailablegenom:[0,2],file:[0,3,2],site:0,getappresult:2,myapi:0,basespacetestfil:0,s_g1_l001_r2_002:0,descript:[0,2],shutil:0,multipartfileupload:2,updateprivileg:2,par:2,disabl:2,develop:[0,1],sphaeroid:0,grant:[0,2],getapptrigg:0,make:[0,1,2],belong:0,createbsdir:2,same:[0,2],setstatu:0,handl:0,mkallberg:0,html:1,"2x151":0,document:[0,1],latexpdf:1,complet:[0,2],byterang:2,http:[0,1],resequenc:0,rais:2,temporari:2,user:[0,2],client_secret:0,appsess:[3,2],chang:[0,2],expand:0,bsauth:0,well:0,without:2,command:0,thi:[0,1,2],everyth:0,identifi:0,paus:0,sample_3:0,getvariantmetadata:2,obtain:0,rest:[0,2],sample_2:0,sample_1:0,human:0,outlin:0,yet:2,web:[0,2],cut:0,easi:0,getanalys:0,point:0,except:2,device_cod:0,add:[0,1],other:2,input:2,modul:[1,3,2],match:2,applic:[0,3,2],appresultfileupload:2,which:2,format:2,read:[0,2],piec:0,getvariantmeta:[0,2],isvalidfileopt:2,getappsessionpropertiesbyid:2,desc:2,applicationactionid:0,specif:[0,2],success:2,filenam:2,should:2,server:[0,2],href:0,necessari:0,either:[0,2],localdir:2,output:0,page:3,interv:[0,2],some:0,localpath:2,intern:2,homo:0,sampl:[0,3,2],analysisfil:0,aureu:0,octet:2,achiev:0,per:2,ucsc:0,larg:2,basespacepi:[0,1,2,3],approv:[0,2],getsamplefilesbyid:2,who:0,run:[0,1,2,3],ntype:0,nmy:0,step:0,apiserv:[0,2],bolt:0,src:0,about:[0,2],obj:2,getuserbyid:[0,2],genomev1:2,hg19:0,getaccess:2,produc:0,own:0,analysis2:0,within:[0,2],automat:0,mydir:0,s_g1_l001_r1_001:0,s_g1_l001_r1_002:0,your:0,getbasespaceapi:0,git:0,byterangeexcept:2,transfer:2,support:2,json:2,custom:2,avail:[0,1,2,3],start:[0,3,2],covmeta:0,includ:[0,2],"var":0,modelnotsupportedexcept:2,individu:2,analysi:[0,3],properli:[1,2],form:2,escherichia:0,projectlist:2,yourproject:0,link:1,oauth:0,"true":[0,2],sdk:[0,1],info:0,made:[0,2],arabidopsi:0,temp:2,possibl:2,"default":2,wish:[0,1,2],maximum:2,record:2,below:0,statussummari:2,processcount:2,"export":0,getappresultsbyproject:2,basespaceurl:0,displaynam:0,creat:[0,1,2,3],flow:0,uri:[0,2],exist:[1,2],kallberg:0,ing:2,check:0,fill:0,again:0,titl:1,when:[0,2],detail:0,invalid:2,field:2,valid:[0,2],tempdir:2,you:[0,1,2],nthe:0,nproject:0,ecoli:0,clean:1,sequenc:[0,2],nafter:0,docstr:1,ngenom:0,log:0,getreferencedsampl:2,sphinx:1,faster:0,directori:[0,1,2],deviceinfo:0,ignor:2,createanalysi:0,time:0,queryparamet:[3,2],profil:2},objtypes:{"0":"py:method","1":"py:class"},titles:["Getting Started","<no title>","Available modules","BaseSpacePy"],objnames:{"0":["py","method","Python method"],"1":["py","class","Python class"]},filenames:["Getting Started","README","Available modules","index"]})
\ No newline at end of file
+Search.setIndex({objects:{"BaseSpacePy.model.File.File":{getIntervalCoverage:[2,0,1,""],isValidFileOption:[2,0,1,""],isInit:[2,0,1,""],downloadFile:[2,0,1,""],getVariantMeta:[2,0,1,""],getCoverageMeta:[2,0,1,""],getFileUrl:[2,0,1,""],filterVariant:[2,0,1,""],getFileS3metadata:[2,0,1,""]},"BaseSpacePy.model.QueryParameters":{QueryParameters:[2,1,1,""]},"BaseSpacePy.model.Run.Run":{isInit:[2,0,1,""],getFiles:[2,0,1,""],getSamples:[2,0,1,""],getAccessStr:[2,0,1,""]},"BaseSpacePy.api.BaseSpaceAPI.BaseSpaceAPI":{getFileById:[2,0,1,""],getAppResultFilesById:[2,0,1,""],getRunFilesById:[2,0,1,""],getUserById:[2,0,1,""],getProjectById:[2,0,1,""],filterVariantSet:[2,0,1,""],getIntervalCoverage:[2,0,1,""],getAppSession:[2,0,1,""],getAppSessionById:[2,0,1,""],getAccessibleRunsByUser:[2,0,1,""],getGenomeById:[2,0,1,""],setAppSessionState:[2,0,1,""],getWebVerificationCode:[2,0,1,""],createAppResult:[2,0,1,""],getSamplePropertiesById:[2,0,1,""],getAppSessionPropertiesById:[2,0,1,""],obtainAccessToken:[2,0,1,""],getAppSessionPropertyByName:[2,0,1,""],multipartFileDownload:[2,0,1,""],multipartFileUpload:[2,0,1,""],updatePrivileges:[2,0,1,""],getAppResultsByProject:[2,0,1,""],getAppResultFiles:[2,0,1,""],getProjectPropertiesById:[2,0,1,""],getVariantMetadata:[2,0,1,""],getRunById:[2,0,1,""],createProject:[2,0,1,""],getFilePropertiesById:[2,0,1,""],getAccess:[2,0,1,""],getFilesBySample:[2,0,1,""],getAppSessionInputsById:[2,0,1,""],getVerificationCode:[2,0,1,""],getAvailableGenomes:[2,0,1,""],getSampleById:[2,0,1,""],appResultFileUpload:[2,0,1,""],fileS3metadata:[2,0,1,""],fileDownload:[2,0,1,""],getSamplesByProject:[2,0,1,""],getCoverageMetaInfo:[2,0,1,""],getProjectByUser:[2,0,1,""],getSampleFilesById:[2,0,1,""],getAppResultById:[2,0,1,""],getRunSamplesById:[2,0,1,""],fileUrl:[2,0,1,""],getAppResultPropertiesById:[2,0,1,""],getRunPropertiesById:[2,0,1,""]},"BaseSpacePy.model.AppSession":{AppSession:[2,1,1,""]},"BaseSpacePy.model.QueryParameters.QueryParameters":{validate:[2,0,1,""]},"BaseSpacePy.model.Project":{Project:[2,1,1,""]},"BaseSpacePy.model.Sample":{Sample:[2,1,1,""]},"BaseSpacePy.model.AppResult.AppResult":{getFiles:[2,0,1,""],uploadFile:[2,0,1,""],isInit:[2,0,1,""],getReferencedSamples:[2,0,1,""],getAccessStr:[2,0,1,""],getReferencedSamplesIds:[2,0,1,""]},"BaseSpacePy.model.Run":{Run:[2,1,1,""]},"BaseSpacePy.api.BaseSpaceAPI":{BaseSpaceAPI:[2,1,1,""]},"BaseSpacePy.model.AppResult":{AppResult:[2,1,1,""]},"BaseSpacePy.model.Sample.Sample":{isInit:[2,0,1,""],getFiles:[2,0,1,""],getAccessStr:[2,0,1,""],getReferencedAppResults:[2,0,1,""]},"BaseSpacePy.model.Project.Project":{isInit:[2,0,1,""],getAppResults:[2,0,1,""],getAccessStr:[2,0,1,""],createAppResult:[2,0,1,""],getSamples:[2,0,1,""]},"BaseSpacePy.model.File":{File:[2,1,1,""]}},terms:{all:[0,2],code:[0,2],queri:[0,3,2],global:[0,3],getfilesbysampl:2,nwe:0,prefix:0,sleep:0,follow:0,getproject:0,depend:2,basespacepy_vx:0,getrunsamplesbyid:2,texliv:1,send:2,hrefcoverag:2,granular:2,present:2,sourc:2,string:[0,2],accesstoken:[0,2],fals:2,account:0,getfilepropertiesbyid:2,myprojects2:0,veri:[0,2],testfile2:0,brows:[0,3,2],getprojectbyid:[0,2],getappresultfilesbyid:2,contenttyp:2,level:2,list:[0,2],upload:[0,3,2],"try":0,item:0,getgenomebyid:[0,2],verif:[0,2],small:2,refer:2,round:2,dir:0,pleas:0,work:0,second:0,pass:2,download:[0,2],further:[0,2],cat:2,append:0,even:0,index:3,filedownload:2,compar:0,neg:2,section:0,abl:0,hrefvari:2,current:[0,2],delet:1,version:[0,1,2],"new":[0,1,2],method:[0,1,2],metadata:2,abov:0,gener:[0,1,2],here:0,shouldn:2,varmeta:0,let:0,ubuntu:0,basespaceauth:0,path:[0,2],getappsessioninputsbyid:2,sinc:1,valu:[0,2],search:3,queur:0,larger:2,prior:0,isinit:2,tauru:0,action:0,implement:2,chrchr2:0,getaccesstoken:0,privilig:0,extra:1,app:[0,2],apt:1,deprec:2,unix:[0,2],api:[0,3,2],getappsessionbyid:2,instal:[0,1],txt:[0,1],"2x26":0,cloud:0,from:[0,2],rattu:0,commun:2,visit:0,two:0,coverag:[0,2],next:0,websit:0,multipartfiledownload:2,call:[0,2],recommend:1,scope:[0,2],type:[0,2],nrun:0,more:[0,2],sort:[0,2],oauthexcept:2,desir:2,relat:[0,2],notic:0,granttyp:2,known:2,actual:2,hiseq:0,partsiz:2,must:[0,2],none:2,retriev:[0,2],local:2,setup:[0,3],launch:0,getlaunchtyp:0,getfil:[0,2],getsamplepropertiesbyid:2,apporpri:0,endor:0,purpos:0,root:[0,2],norvegicu:0,nearest:2,prompt:0,stream:2,give:0,process:[0,2],accept:2,abort:2,want:0,sought:0,unknownparameterexcept:2,getrun:0,end:[0,2],applaunch:0,anoth:0,write:[0,2],how:0,regist:0,updat:[0,1,2],files3metadata:2,referenc:2,max:[0,2],clone:0,timedout:2,variant:[0,2],befor:0,mai:[0,1,2],associ:[0,2],parallel:2,demonstr:0,getfilebyid:[0,2],"short":0,attempt:2,chr2:[0,2],correspond:2,element:2,inform:[0,2],environ:0,thaliana:0,morten:0,varianthead:[0,2],mygenom:0,authorization_cod:2,help:[0,1],over:2,appsessionid:2,through:[0,2],coli:0,paramet:[0,2],alten:0,binari:2,getappresultfilebyid:2,getwebverificationcod:2,window:2,singleproject:0,pythonpath:0,local_dir:2,sapien:0,main:2,non:2,"return":2,thei:2,s_g1_l001_r2_001:0,python:[0,1],auto:1,cover:0,initi:[0,2],mybam:0,mybasespaceapi:0,now:0,introduct:[0,3],fontx:1,multiprocess:0,name:[0,2],edit:1,authent:[0,2],easili:0,token:[0,3,2],mode:2,timeout:2,genom:[0,2],debug:2,fulli:2,mean:[0,2],status:2,getreferencedappresult:2,chunk:2,hard:0,procedur:0,meta:0,expect:0,our:0,special:0,out:0,variabl:2,vcf:[0,3,2],newli:[0,1,2],getreferencedsamplesid:2,querypar:2,content:[1,3,2],uploadfil:[0,2],getprojectbyus:[0,2],etag:2,print:0,model:[3,2],after:[0,2],insid:2,situat:2,plex:0,triggerobj:0,base:[0,1,2],dictionari:2,needsattent:2,org:1,"byte":2,md5:2,thread:2,doctre:1,musculu:0,filter:[0,2],place:[0,2],isn:2,nthese:0,first:0,rang:2,directli:0,onc:[0,2],propertylist:2,number:[0,2],yourself:0,restrict:2,alreadi:[0,2],done:0,triggertyp:0,miss:2,primari:0,getfiles3metadata:2,size:2,createbspath:2,given:2,script:0,data:[0,3,2],top:0,system:2,store:2,option:[0,2],urllib2:0,specifi:[0,2],getappsesss:2,github:0,accompani:0,staphylococcu:0,than:2,endpo:2,downloadfil:[0,2],instanc:[0,2],provid:[0,2],remov:1,tree:[0,3],project:[0,3,2],str:0,posit:[0,2],multipart:2,comput:0,ana:0,fastq:0,filtervariantset:2,argument:2,client_kei:0,myproject:0,packag:0,bacillu:0,manner:0,have:[0,1,2],tabl:3,need:[0,1,2],getfileurl:2,illegalparameterexcept:2,amplicon:0,startpo:2,getrunbyid:2,getintervalcoverag:[0,2],client:[0,2],note:[0,2],also:0,chromosom:[0,2],exampl:[0,2],take:0,indic:3,singl:2,sure:[0,1],modelnotinitializedexcept:2,test:[0,2],object:[0,2],getverificationcod:[0,2],fileurl:2,"class":[0,1,2],latex:1,getsampl:[0,2],url:2,doc:1,request:[0,3,2],obtainaccesstoken:2,v1pre2:0,part:[0,2],getappresultbyid:2,getrunpropertiesbyid:2,phix:0,getrunfilesbyid:2,show:0,text:[0,2],filetyp:2,session:0,saccharomyc:0,permiss:0,redirecturl:2,cereu:0,redirect:2,access:[0,3,2],onli:[0,2],locat:0,createappresult:2,illumina:0,multivaluepropertyappresultslist:2,getappsess:2,transact:0,solut:0,state:[0,2],haven:0,dict:2,analyz:0,folder:[0,2],analys:0,get:[0,1,2,3],familiar:0,stop:2,getsamplebyid:2,chrom:2,gen:0,requir:[0,2],accesstyp:2,enabl:0,undefinedparameterexcept:2,acut:2,remot:2,allgenom:0,gran:0,privileg:0,grab:0,bam:[0,3,2],basespaceapi:[0,2],summari:[0,2],set:2,see:[0,1],result:[0,3,2],respons:2,fail:2,subject:0,statu:[0,2],getappsessionpropertybynam:2,getaccessiblerunsbyus:2,appresult:[3,2],favor:2,written:1,between:0,"import":0,attribut:2,altern:0,kei:[0,2],buckets:0,filtervari:[0,2],popul:2,verification_with_code_uri:0,last:0,cov:[0,2],samplecount:0,region:2,equal:2,createproject:2,etc:2,redirect_uri:2,pdf:1,com:0,getcoveragemeta:[0,2],nsome:0,simpli:0,coveragemetadata:2,can:[0,2],instanti:0,clientsecret:2,applicationact:0,header:2,empti:2,suppli:0,getappresultpropertiesbyid:2,assum:0,devic:[0,2],due:2,been:[0,2],getaccessstr:[0,2],secret:[0,2],trigger:[0,3],rhodobact:0,interest:2,basic:0,addit:0,clientkei:2,getprojectpropertiesbyid:2,getsamplesbyproject:2,getcoveragemetainfo:2,coordin:2,cerevisia:0,repres:2,those:2,"case":2,multi:[0,2],look:0,access_token:0,plain:[0,2],align:2,properti:2,basespac:[0,2],coveragemeta:0,"while":2,na18507:0,myvcf:0,error:2,getappresultfil:2,setappsessionst:2,howev:2,loop:0,getavailablegenom:[0,2],file:[0,3,2],site:0,getappresult:2,myapi:0,basespacetestfil:0,s_g1_l001_r2_002:0,descript:[0,2],shutil:0,multipartfileupload:2,updateprivileg:2,par:2,disabl:2,develop:[0,1],sphaeroid:0,grant:[0,2],getapptrigg:0,make:[0,1,2],belong:0,createbsdir:2,same:[0,2],setstatu:0,handl:0,mkallberg:0,html:1,"2x151":0,document:[0,1],latexpdf:1,complet:[0,2],byterang:2,http:[0,1],resequenc:0,rais:2,temporari:2,user:[0,2],client_secret:0,appsess:[3,2],chang:[0,2],expand:0,bsauth:0,well:0,without:2,command:0,thi:[0,1,2],everyth:0,identifi:0,paus:0,sample_3:0,getvariantmetadata:2,obtain:0,rest:[0,2],sample_2:0,sample_1:0,human:0,outlin:0,yet:2,web:[0,2],cut:0,easi:0,getanalys:0,point:0,except:2,device_cod:0,add:[0,1],other:2,input:2,modul:[1,3,2],match:2,applic:[0,3,2],appresultfileupload:2,which:2,format:2,read:[0,2],piec:0,getvariantmeta:[0,2],isvalidfileopt:2,getappsessionpropertiesbyid:2,desc:2,applicationactionid:0,specif:[0,2],success:2,filenam:2,should:2,server:[0,2],href:0,necessari:0,either:[0,2],localdir:2,output:0,page:3,interv:[0,2],some:0,localpath:2,intern:2,homo:0,sampl:[0,3,2],analysisfil:0,aureu:0,octet:2,achiev:0,per:2,ucsc:0,larg:2,basespacepi:[0,1,2,3],approv:[0,2],getsamplefilesbyid:2,who:0,run:[0,1,2,3],ntype:0,nmy:0,step:0,apiserv:[0,2],bolt:0,src:0,about:[0,2],obj:2,getuserbyid:[0,2],genomev1:2,hg19:0,getaccess:2,produc:0,own:0,analysis2:0,within:[0,2],automat:0,mydir:0,s_g1_l001_r1_001:0,s_g1_l001_r1_002:0,your:0,getbasespaceapi:0,git:0,byterangeexcept:2,transfer:2,support:2,json:2,custom:2,avail:[0,1,2,3],start:[0,3,2],covmeta:0,includ:[0,2],"var":0,modelnotsupportedexcept:2,individu:2,analysi:[0,3],properli:[1,2],form:2,escherichia:0,projectlist:2,yourproject:0,link:1,oauth:0,"true":[0,2],sdk:[0,1],info:0,made:[0,2],arabidopsi:0,temp:2,possibl:2,"default":2,wish:[0,1,2],maximum:2,record:2,below:0,statussummari:2,processcount:2,"export":0,getappresultsbyproject:2,basespaceurl:0,displaynam:0,creat:[0,1,2,3],flow:0,uri:[0,2],exist:[1,2],kallberg:0,ing:2,check:0,fill:0,again:0,titl:1,when:[0,2],detail:0,invalid:2,field:2,valid:[0,2],tempdir:2,you:[0,1,2],nthe:0,nproject:0,ecoli:0,clean:1,sequenc:[0,2],nafter:0,docstr:1,ngenom:0,log:0,getreferencedsampl:2,sphinx:1,faster:0,directori:[0,1,2],deviceinfo:0,ignor:2,createanalysi:0,time:0,queryparamet:[3,2],profil:2},objtypes:{"0":"py:method","1":"py:class"},titles:["Getting Started","<no title>","Available modules","BaseSpacePy"],objnames:{"0":["py","method","Python method"],"1":["py","class","Python class"]},filenames:["Getting Started","README","Available modules","index"]})
\ No newline at end of file
diff --git a/doc/latex/BaseSpacePy.tex b/doc/latex/BaseSpacePy.tex
index 05ae4ff..b230124 100644
--- a/doc/latex/BaseSpacePy.tex
+++ b/doc/latex/BaseSpacePy.tex
@@ -1894,7 +1894,7 @@ \subsection{Availability}
\subsection{Setup}
\label{Getting Started:setup}
-\emph{Requirements:} Python 2.6 with the packages `urllib2', `pycurl', `multiprocessing' and `shutil' available.
+\emph{Requirements:} Python 2.6 with the packages `urllib2', `multiprocessing' and `shutil' available.
The multi-part file upload will currently only run on a unix setup.
diff --git a/examples/0_Browsing.py b/examples/0_Browsing.py
index d90710e..faece72 100644
--- a/examples/0_Browsing.py
+++ b/examples/0_Browsing.py
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,11 +14,10 @@
"""
from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI
-import os
"""
This script demonstrates basic browsing of BaseSpace objects once an access-token
-for global browsing has been obtained.
+for global browsing has been obtained.
"""
"""
@@ -30,8 +29,8 @@
[DEFAULT]
name = my new app
clientKey =
-clientSecret =
-accessToken =
+clientSecret =
+accessToken =
appSessionId =
apiServer = https://api.cloud-hoth.illumina.com/
apiVersion = v1pre3
@@ -49,35 +48,35 @@
myAPI = BaseSpaceAPI(clientKey, clientSecret, apiServer, apiVersion, appSessionId)
else:
myAPI = BaseSpaceAPI(profile='DEFAULT')
-
+
# First, let's grab the genome with id=4
myGenome = myAPI.getGenomeById('4')
-print "\nThe Genome is " + str(myGenome)
-print "We can get more information from the genome object"
-print 'Id: ' + myGenome.Id
-print 'Href: ' + myGenome.Href
-print 'DisplayName: ' + myGenome.DisplayName
+print("\nThe Genome is " + str(myGenome))
+print("We can get more information from the genome object")
+print('Id: ' + myGenome.Id)
+print('Href: ' + myGenome.Href)
+print('DisplayName: ' + myGenome.DisplayName)
# Get a list of all genomes
allGenomes = myAPI.getAvailableGenomes()
-print "\nGenomes \n" + str(allGenomes)
+print("\nGenomes \n" + str(allGenomes))
# Let's have a look at the current user
user = myAPI.getUserById('current')
-print "\nThe current user is \n" + str(user)
+print("\nThe current user is \n" + str(user))
# Now list the projects for this user
myProjects = myAPI.getProjectByUser()
-print "\nThe projects for this user are \n" + str(myProjects)
+print("\nThe projects for this user are \n" + str(myProjects))
# We can also achieve this by making a call using the 'user instance'
myProjects2 = user.getProjects(myAPI)
-print "\nProjects retrieved from the user instance \n" + str(myProjects2)
+print("\nProjects retrieved from the user instance \n" + str(myProjects2))
# List the runs available for the current user
runs = user.getRuns(myAPI)
-print "\nThe runs for this user are \n" + str(runs)
+print("\nThe runs for this user are \n" + str(runs))
# In the same manner we can get a list of accessible user runs
runs = user.getRuns(myAPI)
-print "\nRuns retrieved from user instance \n" + str(runs)
+print("\nRuns retrieved from user instance \n" + str(runs))
diff --git a/examples/1_AccessingFiles.py b/examples/1_AccessingFiles.py
index 7b65b1e..e7388aa 100644
--- a/examples/1_AccessingFiles.py
+++ b/examples/1_AccessingFiles.py
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,16 +14,15 @@
"""
from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI
-import os
"""
-This script demonstrates how to access Samples and AppResults from a projects and how to work with the available
-file data for such instances.
+This script demonstrates how to access Samples and AppResults from a projects and how to work with the available
+file data for such instances.
"""
"""
-NOTE: The coverage and variants API calls below require access to a public
+NOTE: The coverage and variants API calls below require access to a public
dataset. Before running the example, first go to cloud-hoth.illumina.com,
-login, click on Public Data, select the dataset named 'MiSeq B. cereus demo
+login, click on Public Data, select the dataset named 'MiSeq B. cereus demo
data', and click the Import button for the Project named 'BaseSpaceDemo'.
"""
@@ -51,40 +50,40 @@
# Let's list all the AppResults and samples for these projects
for singleProject in myProjects:
- print "# " + str(singleProject)
+ print("# " + str(singleProject))
appResults = singleProject.getAppResults(myAPI)
- print " The App results for project " + str(singleProject) + " are \n\t" + str(appResults)
+ print(" The App results for project " + str(singleProject) + " are \n\t" + str(appResults))
samples = singleProject.getSamples(myAPI)
- print " The samples for project " + str(singleProject) + " are \n\t" + str(samples)
+ print(" The samples for project " + str(singleProject) + " are \n\t" + str(samples))
#
-## we'll take a further look at the files belonging to the sample and
-##analyses from the last project in the loop above
+## we'll take a further look at the files belonging to the sample and
+##analyses from the last project in the loop above
for a in appResults:
- print "# " + a.Id
+ print("# " + a.Id)
ff = a.getFiles(myAPI)
- print ff
+ print(ff)
for s in samples:
- print "Sample " + str(s)
+ print("Sample " + str(s))
ff = s.getFiles(myAPI)
- print ff
+ print(ff)
-## Now let's do some work with files
-## we'll grab a BAM by id and get the coverage for an interval + accompanying meta-data
+## Now let's do some work with files
+## we'll grab a BAM by id and get the coverage for an interval + accompanying meta-data
myBam = myAPI.getFileById('9895890')
-print myBam
+print(myBam)
cov = myBam.getIntervalCoverage(myAPI,'chr','1','100')
-print cov
+print(cov)
try:
covMeta = myBam.getCoverageMeta(myAPI,'chr')
except Exception as e:
- print "Coverage metadata may not be available for this BAM file: %s" % str(e)
+ print("Coverage metadata may not be available for this BAM file: %s" % str(e))
else:
- print covMeta
+ print(covMeta)
#
## and a vcf file
myVCF = myAPI.getFileById('9895892')
varMeta = myVCF.getVariantMeta(myAPI)
-print varMeta
-var = myVCF.filterVariant(myAPI,'chr','1', '25000')
-print var
+print(varMeta)
+var = myVCF.filterVariant(myAPI,'chr','1', '25000')
+print(var)
diff --git a/examples/2_AppTriggering.py b/examples/2_AppTriggering.py
index e38c87f..d9d7d96 100644
--- a/examples/2_AppTriggering.py
+++ b/examples/2_AppTriggering.py
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,11 +14,9 @@
"""
from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI
-import webbrowser
-import time
"""
-This script demonstrates how to retrieve the AppSession object produced
+This script demonstrates how to retrieve the AppSession object produced
when a user initiates an app. Further it's demonstrated how to automatically
generate the scope strings to request access to the data object (a project or a sample)
that the app was triggered to analyze.
@@ -51,30 +49,30 @@
# Using the basespaceApi we can request the appSession object corresponding to the AppSession id supplied
myAppSession = myAPI.getAppSession()
-print myAppSession
+print(myAppSession)
# An app session contains a referal to one or more appLaunchObjects which reference the data module
-# the user launched the app on. This can be a list of projects, samples, or a mixture of objects
-print "\nType of data the app was triggered on can be seen in 'references'"
-print myAppSession.References
+# the user launched the app on. This can be a list of projects, samples, or a mixture of objects
+print("\nType of data the app was triggered on can be seen in 'references'")
+print(myAppSession.References)
# We can also get a handle to the user who started the AppSession
-print "\nWe can get a handle for the user who triggered the app\n" + str(myAppSession.UserCreatedBy)
+print("\nWe can get a handle for the user who triggered the app\n" + str(myAppSession.UserCreatedBy))
# Let's have a closer look at the appSessionLaunchObject
myReference = myAppSession.References[0]
-print "\nWe can get out information such as the href to the launch object:"
-print myReference.HrefContent
-print "\nand the specific type of that object:"
-print myReference.Type
+print("\nWe can get out information such as the href to the launch object:")
+print(myReference.HrefContent)
+print("\nand the specific type of that object:")
+print(myReference.Type)
# Now we will want to ask for more permission for the specific reference object
-print "\nWe can get out the specific project objects by using 'content':"
+print("\nWe can get out the specific project objects by using 'content':")
myReference = myReference.Content
-print myReference
-print "\nThe scope string for requesting read access to the reference object is:"
-print myReference.getAccessStr(scope='write')
+print(myReference)
+print("\nThe scope string for requesting read access to the reference object is:")
+print(myReference.getAccessStr(scope='write'))
# We can easily request write access to the reference object so our App can start contributing analysis
# by default we ask for write permission and authentication for a device
@@ -82,21 +80,21 @@
# We may limit our request to read access only if that's all that is needed
#readAccessMaps = myAPI.getAccess(myReference,accessType='read')
-#print "\nWe get the following access map for the write request"
-#print accessMap
+#print("\nWe get the following access map for the write request")
+#print(accessMap)
## PAUSE HERE
# Have the user visit the verification uri to grant us access
-#print "\nPlease visit the uri within 15 seconds and grant access"
-#print accessMap['verification_with_code_uri']
+#print("\nPlease visit the uri within 15 seconds and grant access")
+#print(accessMap['verification_with_code_uri'])
#webbrowser.open_new(accessMap['verification_with_code_uri'])
#time.sleep(15)
## PAUSE HERE
# Once the user has granted us the access to the object we requested we can
# get the basespace access token and start browsing simply by calling updatePriviliges
-# on the baseSpaceApi instance
+# on the baseSpaceApi instance
#code = accessMap['device_code']
#myAPI.updatePrivileges(code)
-#print "\nThe BaseSpaceAPI instance was update with write privileges"
-#print myAPI
+#print("\nThe BaseSpaceAPI instance was update with write privileges")
+#print(myAPI)
diff --git a/examples/3_Authentication.py b/examples/3_Authentication.py
index 6b13753..fa72b0a 100644
--- a/examples/3_Authentication.py
+++ b/examples/3_Authentication.py
@@ -5,26 +5,28 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
+from __future__ import print_function
-import sys
from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI
import time
-import webbrowser
-import cPickle as Pickle
+import webbrowser
import os
+from six.moves import cPickle as Pickle
+
+
"""
-Demonstrates the basic BaseSpace authentication process The work-flow is as follows:
+Demonstrates the basic BaseSpace authentication process The work-flow is as follows:
scope-request -> user grants access -> browsing data. The scenario is demonstrated both for device and web-based apps.
-Further we demonstrate how a BaseSpaceAPI instance may be preserved across multiple http-request for the same app session using
+Further we demonstrate how a BaseSpaceAPI instance may be preserved across multiple http-request for the same app session using
python's pickle module.
"""
@@ -45,18 +47,18 @@
myAPI = BaseSpaceAPI(clientKey, clientSecret, apiServer, apiVersion, appSessionId)
else:
myAPI = BaseSpaceAPI(profile='DEFAULT')
-
-
+
+
# First, get the verification code and uri for scope 'browse global'
deviceInfo = myAPI.getVerificationCode('browse global')
-print "\n URL for user to visit and grant access: "
-print deviceInfo['verification_with_code_uri']
+print("\n URL for user to visit and grant access: ")
+print(deviceInfo['verification_with_code_uri'])
## PAUSE HERE
# Have the user visit the verification uri to grant us access
-print "\nPlease visit the uri within 15 seconds and grant access"
-print deviceInfo['verification_with_code_uri']
+print("\nPlease visit the uri within 15 seconds and grant access")
+print(deviceInfo['verification_with_code_uri'])
webbrowser.open_new(deviceInfo['verification_with_code_uri'])
time.sleep(15)
## PAUSE HERE
@@ -68,13 +70,13 @@
myAPI.updatePrivileges(code)
# As a reference the provided access-token can be obtained from the BaseSpaceApi object
-print "\nMy Access-token:"
-print myAPI.getAccessToken()
+print("\nMy Access-token:")
+print(myAPI.getAccessToken())
-# Let's try and grab all available genomes with our new api!
+# Let's try and grab all available genomes with our new api!
allGenomes = myAPI.getAvailableGenomes()
-print "\nGenomes \n" + str(allGenomes)
+print("\nGenomes \n" + str(allGenomes))
# If at a later stage we wish to initialize a BaseSpaceAPI object when we already have
@@ -82,8 +84,8 @@
# object using the key-word AccessToken.
myToken = myAPI.getAccessToken()
myAPI.setAccessToken(myToken)
-print "\nA BaseSpaceAPI instance was updated with an access-token: "
-print myAPI
+print("\nA BaseSpaceAPI instance was updated with an access-token: ")
+print(myAPI)
#################### Web-based verification #################################
# The scenario where the authentication is done through a web-browser
@@ -94,8 +96,8 @@
BSapiWeb = BaseSpaceAPI(profile='DEFAULT')
userUrl= BSapiWeb.getWebVerificationCode('browse global','http://localhost',state='myState')
-print "\nHave the user visit:"
-print userUrl
+print("\nHave the user visit:")
+print(userUrl)
webbrowser.open_new(userUrl)
@@ -109,20 +111,20 @@
#################### Storing BaseSpaceApi using python's pickle module #################################
"""
-It may sometimes be useful to preserve certain api objects across a series of http requests from the same user-session.
+It may sometimes be useful to preserve certain api objects across a series of http requests from the same user-session.
Here we demonstrate how the Python pickle module may be used to achieve this end.
The example will be for an instance of BaseSpaceAPI, but the same technique may be used for BaseSpaceAuth.
-In fact, a single instance of BaseSpaceAuth would be enough for a single App and could be shared by all http-requests, as the identity of
-this object is only given by the client_key and client_secret.
+In fact, a single instance of BaseSpaceAuth would be enough for a single App and could be shared by all http-requests, as the identity of
+this object is only given by the client_key and client_secret.
(There is, of course, no problem in having multiple identical BaseSpaceAuth instances).
"""
# Get current user
user= myAPI.getUserById('current')
-print user
-print myAPI
+print(user)
+print(myAPI)
#### Here some work goes on
@@ -134,9 +136,9 @@
Pickle.dump(myAPI, f)
f.close()
-# Imagine the current request is done, we will simulate this by deleting the api instance
+# Imagine the current request is done, we will simulate this by deleting the api instance
myAPI = None
-print "\nTry printing the removed API, we get: " + str(myAPI)
+print("\nTry printing the removed API, we get: " + str(myAPI))
# Next request in the session with id = id123 comes in
@@ -145,10 +147,9 @@
f = open(mySessionId)
myAPI = Pickle.load(f)
f.close()
- print
- print "We got the API back!"
- print myAPI
+ print()
+ print("We got the API back!")
+ print(myAPI)
else:
- print "Looks like we haven't stored anything for this session yet"
+ print("Looks like we haven't stored anything for this session yet")
# create a BaseSpaceAPI for the first time
-
diff --git a/examples/4_AppResultUpload.py b/examples/4_AppResultUpload.py
index e5c2017..e36d7ef 100644
--- a/examples/4_AppResultUpload.py
+++ b/examples/4_AppResultUpload.py
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,11 +14,10 @@
"""
from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI
-import os
"""
This script demonstrates how to create a new AppResults object, change its state
-and upload result files to it and download files from it.
+and upload result files to it and download files from it.
"""
@@ -43,47 +42,47 @@
# Now we'll do some work of our own. First get a project to work on
# we'll need write permission, for the project we are working on
-# meaning we will need get a new token and instantiate a new BaseSpaceAPI
+# meaning we will need get a new token and instantiate a new BaseSpaceAPI
p = myAPI.getProjectById('89')
# Assuming we have write access to the project
-# we will list the current App Results for the project
+# we will list the current App Results for the project
appRes = p.getAppResults(myAPI,statuses=['Running'])
-print "\nThe current running AppResults are \n" + str(appRes)
+print("\nThe current running AppResults are \n" + str(appRes))
# now let's do some work!
# to create an appResults for a project, simply give the name and description
appResults = p.createAppResult(myAPI,"testing","this is my results",appSessionId='')
-print "\nSome info about our new app results"
-print appResults
-print appResults.Id
-print "\nThe app results also comes with a reference to our AppSession"
+print("\nSome info about our new app results")
+print(appResults)
+print(appResults.Id)
+print("\nThe app results also comes with a reference to our AppSession")
myAppSession = appResults.AppSession
-print myAppSession
+print(myAppSession)
# we can change the status of our AppSession and add a status-summary as follows
myAppSession.setStatus(myAPI,'needsattention',"We worked hard, but encountered some trouble.")
-print "\nAfter a change of status of the app sessions we get\n" + str(myAppSession)
+print("\nAfter a change of status of the app sessions we get\n" + str(myAppSession))
# we'll set our appSession back to running so we can do some more work
myAppSession.setStatus(myAPI,'running',"Back on track")
-### Let's list all AppResults again and see if our new object shows up
+### Let's list all AppResults again and see if our new object shows up
appRes = p.getAppResults(myAPI,statuses=['Running'])
-print "\nThe updated app results are \n" + str(appRes)
+print("\nThe updated app results are \n" + str(appRes))
appResult2 = myAPI.getAppResultById(appResults.Id)
-print appResult2
+print(appResult2)
-## Now we will make another AppResult
+## Now we will make another AppResult
## and try to upload a file to it
appResults2 = p.createAppResult(myAPI,"My second AppResult","This one I will upload to")
appResults2.uploadFile(myAPI, '/home/mkallberg/Desktop/testFile2.txt', 'BaseSpaceTestFile.txt', '/mydir/', 'text/plain')
-print "\nMy AppResult number 2 \n" + str(appResults2)
+print("\nMy AppResult number 2 \n" + str(appResults2))
## let's see if our new file made it
appResultFiles = appResults2.getFiles(myAPI)
-print "\nThese are the files in the appResult"
-print appResultFiles
+print("\nThese are the files in the appResult")
+print(appResultFiles)
f = appResultFiles[-1]
# we can even download our newly uploaded file
diff --git a/examples/5_Purchasing.py b/examples/5_Purchasing.py
index d9fbea0..53a224f 100644
--- a/examples/5_Purchasing.py
+++ b/examples/5_Purchasing.py
@@ -5,17 +5,15 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
-import os
import webbrowser
import time
-from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI
from BaseSpacePy.api.BillingAPI import BillingAPI
from BaseSpacePy.model.QueryParametersPurchasedProduct import QueryParametersPurchasedProduct as qpp
"""
@@ -41,7 +39,7 @@
if not client_key:
raise Exception("Please fill in client values (in the script) before running the script")
-# Create a client for making calls for this user session
+# Create a client for making calls for this user session
billAPI = BillingAPI(BaseSpaceStoreUrl, version, AppSessionId, AccessToken=accessToken)
# create a non-consumable purchase
@@ -49,43 +47,43 @@
# create a consumable purchase, and associated it with an AppSession
# also add tags to provide (fake) details about the purchase
-print "\nCreating purchase\n"
+print("\nCreating purchase\n")
purch = billAPI.createPurchase({'id':product_id,'quantity':4, 'tags':["test","test_tag"] }, AppSessionId)
# record the purchase Id and RefundSecret for refunding later
purchaseId = purch.Id
refundSecret = purch.RefundSecret
-print "Now complete the purchase in your web browser"
-print "CLOSE the browser window/tab after you click 'Purchase' (and don't proceed into the app)"
+print("Now complete the purchase in your web browser")
+print("CLOSE the browser window/tab after you click 'Purchase' (and don't proceed into the app)")
time.sleep(3)
## PAUSE HERE
-print "Opening: " + purch.HrefPurchaseDialog
+print("Opening: " + purch.HrefPurchaseDialog)
webbrowser.open_new(purch.HrefPurchaseDialog)
-print "Waiting 30 seconds..."
+print("Waiting 30 seconds...")
time.sleep(30)
## PAUSE HERE
-print "\nConfirm the purchase"
+print("\nConfirm the purchase")
post_purch = billAPI.getPurchaseById(purchaseId)
-print "The status of the purchase is now: " + post_purch.Status
+print("The status of the purchase is now: " + post_purch.Status)
-print "\nRefunding the Purchase"
+print("\nRefunding the Purchase")
# note we must use the same access token that was provided used for the purchase
refunded_purchase = billAPI.refundPurchase(purchaseId, refundSecret, comment='the product did not function well as a frisbee')
-print "\nGetting all purchases for the current user with the tags we used for the purchase above"
+print("\nGetting all purchases for the current user with the tags we used for the purchase above")
purch_prods = billAPI.getUserProducts(Id='current', queryPars=qpp( {'Tags':'test,test_tag'} ))
if not len(purch_prods):
- print "\nHmmm, didn't find any purchases with these tags. Did everything go OK above?\n"
+ print("\nHmmm, didn't find any purchases with these tags. Did everything go OK above?\n")
else:
- print "\nFor the first of these purchases:\n"
- print "Purchase Name: " + purch_prods[0].Name
- print "Purchase Price: " + purch_prods[0].Price
- print "Purchase Quantity: " + purch_prods[0].Quantity
- print "Tags: " + str(purch_prods[0].Tags)
+ print("\nFor the first of these purchases:\n")
+ print("Purchase Name: " + purch_prods[0].Name)
+ print("Purchase Price: " + purch_prods[0].Price)
+ print("Purchase Quantity: " + purch_prods[0].Quantity)
+ print("Tags: " + str(purch_prods[0].Tags))
# Get the refund status of the purchase
- print "\nGetting the (refunded) Purchase we just made"
+ print("\nGetting the (refunded) Purchase we just made")
get_purch = billAPI.getPurchaseById(purch_prods[0].PurchaseId)
- print "Refund Status: " + get_purch.RefundStatus + "\n"
+ print("Refund Status: " + get_purch.RefundStatus + "\n")
diff --git a/runtests.sh b/runtests.sh
new file mode 100755
index 0000000..2993349
--- /dev/null
+++ b/runtests.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+set -e
+
+mkdir ~/.basespace
+
+cp test/dotbasespace/*.bash ~/.basespace
+cp test/dotbasespace/*.json ~/.basespace
+
+cd data
+curl -O https://s3.amazonaws.com/basespace-sdk-unit-test-data/BC-12_S12_L001_R2_001.fastq.gz
+cd ..
+
+# ...
+cat test/dotbasespace/unit_tests.cfg | sed "s/__ACCESS_TOKEN__/$ACCESS_TOKEN/" > ~/.basespace/unit_tests.cfg
+cp ~/.basespace/unit_tests.cfg ~/.basespace/default.cfg
+
+pip install pyflakes
+
+echo
+echo "Static analysis warnings from pyflakes:"
+echo
+# exclude doc directory because those files are auto-generated
+# the "|| true" at the end causes CI not to fail. In future
+# you could remove that if you want pyflakes warnings to break the build.
+find . \( -path ./doc -o -path ./src/build \) -prune -o -name '*.py' -print | xargs pyflakes || true
+
+# TODO add stricter flake8 checking here
+# (checks for proper formatting of code in compliance with PEP8)
+
+
+echo
+echo "Unit test output:"
+echo
+
+
+python test/unit_tests.py
+
+
+exit $?
diff --git a/src/BaseSpacePy/api/APIClient.develop.py b/src/BaseSpacePy/api/APIClient.develop.py
new file mode 100644
index 0000000..52d32b4
--- /dev/null
+++ b/src/BaseSpacePy/api/APIClient.develop.py
@@ -0,0 +1,255 @@
+
+import sys
+import os
+import re
+import urllib
+import urllib2
+import io
+import cStringIO
+import json
+from subprocess import *
+import subprocess
+import dateutil.parser
+from warnings import warn
+from BaseSpacePy.model import *
+from BaseSpacePy.api.BaseSpaceException import RestMethodException, ServerResponseException
+
+
+class APIClient:
+ def __init__(self, AccessToken, apiServerAndVersion, userAgent=None, timeout=10):
+ '''
+ Initialize the API instance
+
+ :param AccessToken: an access token
+ :param apiServerAndVersion: the URL of the BaseSpace api server with api version
+ :param timeout: (optional) the timeout in seconds for each request made, default 10
+ '''
+ self.apiKey = AccessToken
+ self.apiServerAndVersion = apiServerAndVersion
+ self.userAgent = userAgent
+ self.timeout = timeout
+
+ def __forcePostCall__(self, resourcePath, postData, headers):
+ '''
+ For forcing a REST POST request (seems to be used when POSTing with no post data)
+
+ :param resourcePath: the url to call, including server address and api version
+ :param postData: a dictionary of data to post
+ :param headers: a dictionary of header key/values to include in call
+ :returns: server response (a string containing json)
+ '''
+ import requests
+ # this tries to clean up the output at the expense of letting the user know they're in an insecure context...
+ try:
+ requests.packages.urllib3.disable_warnings()
+ except:
+ pass
+ import logging
+ logging.getLogger("requests").setLevel(logging.WARNING)
+ encodedPost = urllib.urlencode(postData)
+ resourcePath = "%s?%s" % (resourcePath, encodedPost)
+ response = requests.post(resourcePath, data=json.dumps(postData), headers=headers)
+ return response.text
+
+ def __putCall__(self, resourcePath, headers, data):
+ '''
+ Performs a REST PUT call to the API server.
+
+ :param resourcePath: the url to call, including server address and api version
+ :param headers: a dictionary of header key/values to include in call
+ :param transFile: the name of the file containing only data to be PUT
+ :returns: server response (a string containing upload status message (from curl?) followed by json response)
+ '''
+ # headerPrep = [k + ':' + headers[k] for k in headers.keys()]
+ # cmd = 'curl -H "x-access-token:' + self.apiKey + '" -H "Content-MD5:' + headers['Content-MD5'].strip() +'" -T "'+ transFile +'" -X PUT ' + resourcePath
+ # p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
+ # output = p.stdout.read()
+ # print output
+ # return output
+ import requests
+ put_headers = {
+ 'Content-MD5' : headers['Content-MD5'].strip(),
+ 'x-access-token': self.apiKey
+ }
+ put_val = requests.put(resourcePath, data, headers=put_headers)
+ if put_val.status_code != 200:
+ raise ServerResponseException("Multi-part upload: Server return code %s with error %s" % (put_val.status_code, put_val.reason))
+ return put_val.text
+
+ def callAPI(self, resourcePath, method, queryParams, postData, headerParams=None, forcePost=False):
+ '''
+ Call a REST API and return the server response.
+
+ An access token header is automatically added.
+ If a Content-Type header isn't included, one will be added with 'application/json' (except for PUT and forcePost calls).
+ Query parameters with values of None aren't sent to the server.
+ Server errors are to be handled by the caller (returned response contains error codes/msgs).
+
+ :param resourcePath: the url to call, not including server address and api version
+ :param method: REST method, including GET, POST (and forcePost, see below), and PUT (DELETE not yet supported)
+ :param queryParams: dictionary of query parameters to be added to url, except for forcePost where they are added as 'postData'; not used for PUT calls
+ :param postData: for POST calls, a dictionary to post; not used for forcePost calls; for PUT calls, name of file to put
+ :param headerParams: (optional) a dictionary of header data, default None
+ :param forcePost: (optional) 'force' a POST call using curl (instead of urllib), default False
+
+ :raises RestMethodException: for unrecognized REST method
+ :raises ServerResponseException: for errors in parsing json response from server, and for urlerrors from the opening url
+ :returns: Server response deserialized to a python object (dict)
+ '''
+ url = self.apiServerAndVersion + resourcePath
+ headers = {}
+ if self.userAgent:
+ headers['User-Agent'] = self.userAgent
+ if headerParams:
+ for param, value in headerParams.iteritems():
+ headers[param] = value
+ # specify the content type
+ if not headers.has_key('Content-Type') and not method=='PUT' and not forcePost:
+ headers['Content-Type'] = 'application/json'
+ # include access token in header
+ headers['Authorization'] = 'Bearer ' + self.apiKey
+
+ data = None
+ if method == 'GET':
+ if queryParams:
+ # Need to remove None values, these should not be sent
+ sentQueryParams = {}
+ for param, value in queryParams.iteritems():
+ if value != None:
+ sentQueryParams[param] = value
+ url = url + '?' + urllib.urlencode(sentQueryParams)
+ request = urllib2.Request(url=url, headers=headers)
+ elif method in ['POST', 'PUT', 'DELETE']:
+ if queryParams:
+ # Need to remove None values, these should not be sent
+ sentQueryParams = {}
+ for param, value in queryParams.iteritems():
+ if value != None:
+ sentQueryParams[param] = value
+ forcePostUrl = url
+ url = url + '?' + urllib.urlencode(sentQueryParams)
+ data = postData
+ if data:
+ if type(postData) not in [str, int, float, bool]:
+ data = json.dumps(postData)
+ if not forcePost:
+ if data and not len(data):
+ data='\n' # temp fix, in case is no data in the file, to prevent post request from failing
+ request = urllib2.Request(url=url, headers=headers, data=data)#,timeout=self.timeout)
+ else:
+ response = self.__forcePostCall__(forcePostUrl, sentQueryParams, headers)
+ if method in ['PUT', 'DELETE']:
+ if method == 'DELETE':
+ raise NotImplementedError('DELETE REST API calls aren\'t currently supported')
+ response = self.__putCall__(url, headers, data)
+ response = response.split()[-1] # discard upload status msg (from curl put?)
+ else:
+ raise RestMethodException('Method ' + method + ' is not recognized.')
+
+ # Make the request
+ if not forcePost and not method in ['PUT', 'DELETE']: # the normal case
+ try:
+ response = urllib2.urlopen(request, timeout=self.timeout).read()
+ except urllib2.HTTPError as e:
+ response = e.read() # treat http error as a response (handle in caller)
+ except urllib2.URLError as e:
+ raise ServerResponseException('URLError: ' + str(e))
+ try:
+ data = json.loads(response)
+ except ValueError as e:
+ raise ServerResponseException('Error decoding json in server response')
+ return data
+
+ def deserialize(self, obj, objClass):
+ """
+ Deserialize a JSON string into a BaseSpacePy object.
+
+ :param obj: A dictionary (or object?) to be deserialized into a class (objClass); or a value to be passed into a new native python type (objClass)
+ :param objClass: A class object or native python type for the deserialized object, or a string of a class name or native python type. (eg, Project.Project, int, 'Project', 'int')
+ :returns: A deserialized object
+ """
+ # Create an object class from objClass, if a string was passed in
+ # Avoid native python types 'file'
+ if type(objClass) == str:
+ try:
+ if (not str(objClass)=='File'):
+ objClass = eval(objClass.lower())
+ else:
+ objClass = eval(objClass + '.' + objClass)
+ except NameError: # not a native type, must be model class
+ objClass = eval(objClass + '.' + objClass)
+
+ # Create an instance of the object class
+ # If the instance is a native python type, return it
+ if objClass in [str, int, float, bool]:
+ return objClass(obj)
+ instance = objClass()
+
+ # For every swaggerType in the instance that is also in the passed-in obj,
+ # set the instance value for native python types,
+ # or recursively deserialize class instances.
+ # For dynamic types, substitute real class after looking up 'Type' value.
+ # For lists, deserialize all members of a list, including lists of lists (though not list of list of list...).
+ # For datetimes, convert to a readable output string
+ for attr, attrType in instance.swaggerTypes.iteritems():
+ if attr in obj:
+ value = obj[attr]
+ if attrType in ['str', 'int', 'float', 'bool']:
+ attrType = eval(attrType)
+ try:
+ value = attrType(value)
+ except UnicodeEncodeError:
+ value = unicode(value)
+ setattr(instance, attr, value)
+ elif attrType == 'DynamicType':
+ try:
+ model_name = instance._dynamicType[value['Type']]
+ except KeyError:
+ pass
+ # suppress this warning, which is caused by a bug in BaseSpace
+ #warn("Warning - unrecognized dynamic type: " + value['Type'])
+ else:
+ setattr(instance, attr, self.deserialize(value, model_name))
+ elif 'list<' in attrType:
+ match = re.match('list<(.*)>', attrType)
+ subClass = match.group(1)
+ subValues = []
+
+ # lists of dynamic type
+ if subClass == 'DynamicType':
+ for subValue in value:
+ try:
+ new_type = instance._dynamicType[subValue['Type']]
+ except KeyError:
+ pass
+ # suppress this warning, which is caused by a bug in BaseSpace
+ #warn("Warning - unrecognized (list of) dynamic types: " + subValue['Type'])
+ else:
+ subValues.append(self.deserialize(subValue, new_type))
+ setattr(instance, attr, subValues)
+ # typical lists
+ else:
+ for subValue in value:
+ subValues.append(self.deserialize(subValue, subClass))
+ setattr(instance, attr, subValues)
+ # list of lists (e.g. map[] property type)
+ elif 'listoflists<' in attrType:
+ match = re.match('listoflists<(.*)>', attrType)
+ subClass = match.group(1)
+ outvals = []
+ for outval in value:
+ invals = []
+ for inval in outval:
+ invals.append(self.deserialize(inval, subClass))
+ outvals.append(invals)
+ setattr(instance, attr, outvals)
+
+ elif attrType=='dict':
+ setattr(instance, attr, value)
+ elif attrType=='datetime':
+ dt = dateutil.parser.parse(value)
+ setattr(instance, attr, dt)
+ else:
+ # recursive call with attribute type
+ setattr(instance, attr, self.deserialize(value, attrType))
+ return instance
diff --git a/src/BaseSpacePy/api/APIClient.passes2.py b/src/BaseSpacePy/api/APIClient.passes2.py
new file mode 100644
index 0000000..abf1ef5
--- /dev/null
+++ b/src/BaseSpacePy/api/APIClient.passes2.py
@@ -0,0 +1,270 @@
+
+import sys
+import os
+import re
+import urllib
+import urllib2
+import io
+import cStringIO
+import json
+import logging
+from subprocess import *
+import subprocess
+import dateutil.parser
+from warnings import warn
+from BaseSpacePy.model import *
+from BaseSpacePy.api.BaseSpaceException import RestMethodException, ServerResponseException
+
+from six.moves import urllib as six_urllib
+import six
+
+class APIClient:
+ def __init__(self, AccessToken, apiServerAndVersion, userAgent=None, timeout=10):
+ '''
+ Initialize the API instance
+
+ :param AccessToken: an access token
+ :param apiServerAndVersion: the URL of the BaseSpace api server with api version
+ :param timeout: (optional) the timeout in seconds for each request made, default 10
+ '''
+ self.apiKey = AccessToken
+ self.apiServerAndVersion = apiServerAndVersion
+ self.userAgent = userAgent
+ self.timeout = timeout
+
+ def __forcePostCall__(self, resourcePath, postData, headers):
+ '''
+ For forcing a REST POST request (seems to be used when POSTing with no post data)
+
+ :param resourcePath: the url to call, including server address and api version
+ :param postData: a dictionary of data to post
+ :param headers: a dictionary of header key/values to include in call
+ :returns: server response (a string containing json)
+ '''
+ import requests
+ # this tries to clean up the output at the expense of letting the user know they're in an insecure context...
+ try:
+ requests.packages.urllib3.disable_warnings()
+ except:
+ pass
+ import logging
+ logging.getLogger("requests").setLevel(logging.WARNING)
+ encodedPost = urllib.urlencode(postData)
+ # encodedPost = six_urllib.parse.urlencode(postData)
+ resourcePath = "%s?%s" % (resourcePath, encodedPost)
+ response = requests.post(resourcePath, data=json.dumps(postData), headers=headers)
+ return response.text
+
+ def __putCall__(self, resourcePath, headers, data):
+ '''
+ Performs a REST PUT call to the API server.
+
+ :param resourcePath: the url to call, including server address and api version
+ :param headers: a dictionary of header key/values to include in call
+ :param transFile: the name of the file containing only data to be PUT
+ :returns: server response (a string containing upload status message (from curl?) followed by json response)
+ '''
+ # headerPrep = [k + ':' + headers[k] for k in headers.keys()]
+ # cmd = 'curl -H "x-access-token:' + self.apiKey + '" -H "Content-MD5:' + headers['Content-MD5'].strip() +'" -T "'+ transFile +'" -X PUT ' + resourcePath
+ # p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
+ # output = p.stdout.read()
+ # print output
+ # return output
+ import requests
+ put_headers = {
+ 'Content-MD5' : headers['Content-MD5'].strip(),
+ 'x-access-token': self.apiKey
+ }
+ put_val = requests.put(resourcePath, data, headers=put_headers)
+ if put_val.status_code != 200:
+ raise ServerResponseException("Multi-part upload: Server return code %s with error %s" % (put_val.status_code, put_val.reason))
+ return put_val.text
+
+ def callAPI(self, resourcePath, method, queryParams, postData, headerParams=None, forcePost=False):
+ '''
+ Call a REST API and return the server response.
+
+ An access token header is automatically added.
+ If a Content-Type header isn't included, one will be added with 'application/json' (except for PUT and forcePost calls).
+ Query parameters with values of None aren't sent to the server.
+ Server errors are to be handled by the caller (returned response contains error codes/msgs).
+
+ :param resourcePath: the url to call, not including server address and api version
+ :param method: REST method, including GET, POST (and forcePost, see below), and PUT (DELETE not yet supported)
+ :param queryParams: dictionary of query parameters to be added to url, except for forcePost where they are added as 'postData'; not used for PUT calls
+ :param postData: for POST calls, a dictionary to post; not used for forcePost calls; for PUT calls, name of file to put
+ :param headerParams: (optional) a dictionary of header data, default None
+ :param forcePost: (optional) 'force' a POST call using curl (instead of urllib), default False
+
+ :raises RestMethodException: for unrecognized REST method
+ :raises ServerResponseException: for errors in parsing json response from server, and for urlerrors from the opening url
+ :returns: Server response deserialized to a python object (dict)
+ '''
+ url = self.apiServerAndVersion + resourcePath
+ headers = {}
+ if self.userAgent:
+ headers['User-Agent'] = self.userAgent
+ if headerParams:
+ for param, value in headerParams.iteritems():
+ # for param, value in six.iteritems(headerParams):
+ headers[param] = value
+ # specify the content type
+ if not headers.has_key('Content-Type') and not method=='PUT' and not forcePost:
+ # if not 'Content-Type' in headers and not method=='PUT' and not forcePost:
+ headers['Content-Type'] = 'application/json'
+ # include access token in header
+ headers['Authorization'] = 'Bearer ' + self.apiKey
+
+ data = None
+ if method == 'GET':
+ if queryParams:
+ # Need to remove None values, these should not be sent
+ sentQueryParams = {}
+ for param, value in queryParams.iteritems():
+ if value != None:
+ sentQueryParams[param] = value
+ url = url + '?' + urllib.urlencode(sentQueryParams)
+ # url = url + '?' + six_urllib.parse.urlencode(sentQueryParams)
+ request = urllib2.Request(url=url, headers=headers)
+ # request = six_urllib.request.Request(url=url, headers=headers)
+ elif method in ['POST', 'PUT', 'DELETE']:
+ if queryParams:
+ # Need to remove None values, these should not be sent
+ sentQueryParams = {}
+ for param, value in queryParams.iteritems():
+ if value != None:
+ sentQueryParams[param] = value
+ forcePostUrl = url
+ url = url + '?' + urllib.urlencode(sentQueryParams)
+ # url = url + '?' + six_urllib.parse.urlencode(sentQueryParams)
+ data = postData
+ if data:
+ if type(postData) not in [str, int, float, bool]:
+ data = json.dumps(postData)
+ if not forcePost:
+ if data and not len(data):
+ data='\n' # temp fix, in case is no data in the file, to prevent post request from failing
+ # THIS IS IT!!!!! :
+ if isinstance(data, unicode):
+ # logging.info("dante data class is %s" % data.__class__)
+ data = data.encode('utf-8')
+ # pass
+
+ request = urllib2.Request(url=url, headers=headers, data=data)#,timeout=self.timeout)
+ else:
+ response = self.__forcePostCall__(forcePostUrl, sentQueryParams, headers)
+ if method in ['PUT', 'DELETE']:
+ if method == 'DELETE':
+ raise NotImplementedError('DELETE REST API calls aren\'t currently supported')
+ response = self.__putCall__(url, headers, data)
+ response = response.split()[-1] # discard upload status msg (from curl put?)
+ else:
+ raise RestMethodException('Method ' + method + ' is not recognized.')
+
+ # Make the request
+ if not forcePost and not method in ['PUT', 'DELETE']: # the normal case
+ try:
+ response = urllib2.urlopen(request, timeout=self.timeout).read()
+ except urllib2.HTTPError as e:
+ response = e.read() # treat http error as a response (handle in caller)
+ except urllib2.URLError as e:
+ raise ServerResponseException('URLError: ' + str(e))
+ try:
+ data = json.loads(response)
+ except ValueError as e:
+ raise ServerResponseException('Error decoding json in server response')
+ return data
+
+ def deserialize(self, obj, objClass):
+ """
+ Deserialize a JSON string into a BaseSpacePy object.
+
+ :param obj: A dictionary (or object?) to be deserialized into a class (objClass); or a value to be passed into a new native python type (objClass)
+ :param objClass: A class object or native python type for the deserialized object, or a string of a class name or native python type. (eg, Project.Project, int, 'Project', 'int')
+ :returns: A deserialized object
+ """
+ # Create an object class from objClass, if a string was passed in
+ # Avoid native python types 'file'
+ if type(objClass) == str:
+ try:
+ if (not str(objClass)=='File'):
+ objClass = eval(objClass.lower())
+ else:
+ objClass = eval(objClass + '.' + objClass)
+ except NameError: # not a native type, must be model class
+ objClass = eval(objClass + '.' + objClass)
+
+ # Create an instance of the object class
+ # If the instance is a native python type, return it
+ if objClass in [str, int, float, bool]:
+ return objClass(obj)
+ instance = objClass()
+
+ # For every swaggerType in the instance that is also in the passed-in obj,
+ # set the instance value for native python types,
+ # or recursively deserialize class instances.
+ # For dynamic types, substitute real class after looking up 'Type' value.
+ # For lists, deserialize all members of a list, including lists of lists (though not list of list of list...).
+ # For datetimes, convert to a readable output string
+ for attr, attrType in instance.swaggerTypes.iteritems():
+ if attr in obj:
+ value = obj[attr]
+ if attrType in ['str', 'int', 'float', 'bool']:
+ attrType = eval(attrType)
+ try:
+ value = attrType(value)
+ except UnicodeEncodeError:
+ value = unicode(value)
+ setattr(instance, attr, value)
+ elif attrType == 'DynamicType':
+ try:
+ model_name = instance._dynamicType[value['Type']]
+ except KeyError:
+ pass
+ # suppress this warning, which is caused by a bug in BaseSpace
+ #warn("Warning - unrecognized dynamic type: " + value['Type'])
+ else:
+ setattr(instance, attr, self.deserialize(value, model_name))
+ elif 'list<' in attrType:
+ match = re.match('list<(.*)>', attrType)
+ subClass = match.group(1)
+ subValues = []
+
+ # lists of dynamic type
+ if subClass == 'DynamicType':
+ for subValue in value:
+ try:
+ new_type = instance._dynamicType[subValue['Type']]
+ except KeyError:
+ pass
+ # suppress this warning, which is caused by a bug in BaseSpace
+ #warn("Warning - unrecognized (list of) dynamic types: " + subValue['Type'])
+ else:
+ subValues.append(self.deserialize(subValue, new_type))
+ setattr(instance, attr, subValues)
+ # typical lists
+ else:
+ for subValue in value:
+ subValues.append(self.deserialize(subValue, subClass))
+ setattr(instance, attr, subValues)
+ # list of lists (e.g. map[] property type)
+ elif 'listoflists<' in attrType:
+ match = re.match('listoflists<(.*)>', attrType)
+ subClass = match.group(1)
+ outvals = []
+ for outval in value:
+ invals = []
+ for inval in outval:
+ invals.append(self.deserialize(inval, subClass))
+ outvals.append(invals)
+ setattr(instance, attr, outvals)
+
+ elif attrType=='dict':
+ setattr(instance, attr, value)
+ elif attrType=='datetime':
+ dt = dateutil.parser.parse(value)
+ setattr(instance, attr, dt)
+ else:
+ # recursive call with attribute type
+ setattr(instance, attr, self.deserialize(value, attrType))
+ return instance
diff --git a/src/BaseSpacePy/api/APIClient.py b/src/BaseSpacePy/api/APIClient.py
index d068338..5758dff 100644
--- a/src/BaseSpacePy/api/APIClient.py
+++ b/src/BaseSpacePy/api/APIClient.py
@@ -2,24 +2,24 @@
import sys
import os
import re
-import urllib
-import urllib2
import io
-import cStringIO
import json
-from subprocess import *
-import subprocess
import dateutil.parser
from warnings import warn
from BaseSpacePy.model import *
from BaseSpacePy.api.BaseSpaceException import RestMethodException, ServerResponseException
+import six
+from six import moves
+from six.moves import urllib
+
+
class APIClient:
def __init__(self, AccessToken, apiServerAndVersion, userAgent=None, timeout=10):
'''
Initialize the API instance
-
+
:param AccessToken: an access token
:param apiServerAndVersion: the URL of the BaseSpace api server with api version
:param timeout: (optional) the timeout in seconds for each request made, default 10
@@ -31,56 +31,60 @@ def __init__(self, AccessToken, apiServerAndVersion, userAgent=None, timeout=10)
def __forcePostCall__(self, resourcePath, postData, headers):
'''
- For forcing a REST POST request using pycurl (seems to be used when POSTing with no post data)
-
+ For forcing a REST POST request (seems to be used when POSTing with no post data)
+
:param resourcePath: the url to call, including server address and api version
:param postData: a dictionary of data to post
:param headers: a dictionary of header key/values to include in call
:returns: server response (a string containing json)
'''
import requests
- # pycurl is hard to get working, so best to cauterise it into only the functions where it is needed
- # import pycurl
- # postData = [(p,postData[p]) for p in postData]
- # headerPrep = [k + ':' + headers[k] for k in headers.keys()]
- # response = cStringIO.StringIO()
- # c = pycurl.Curl()
- # c.setopt(pycurl.URL,resourcePath + '?' + post)
- # c.setopt(pycurl.HTTPHEADER, headerPrep)
- # c.setopt(pycurl.POST, 1)
- # c.setopt(pycurl.POSTFIELDS, post)
- # c.setopt(c.WRITEFUNCTION, response.write)
- # c.perform()
- # c.close()
- # return response.getvalue()
- encodedPost = urllib.urlencode(postData)
+ # this tries to clean up the output at the expense of letting the user know they're in an insecure context...
+ try:
+ requests.packages.urllib3.disable_warnings()
+ except:
+ pass
+ import logging
+ logging.getLogger("requests").setLevel(logging.WARNING)
+ encodedPost = urllib.parse.urlencode(postData)
resourcePath = "%s?%s" % (resourcePath, encodedPost)
response = requests.post(resourcePath, data=json.dumps(postData), headers=headers)
return response.text
- def __putCall__(self, resourcePath, headers, transFile):
+ def __putCall__(self, resourcePath, headers, data):
'''
Performs a REST PUT call to the API server.
-
- :param resourcePath: the url to call, including server address and api version
- :param headers: a dictionary of header key/values to include in call
+
+ :param resourcePath: the url to call, including server address and api version
+ :param headers: a dictionary of header key/values to include in call
:param transFile: the name of the file containing only data to be PUT
:returns: server response (a string containing upload status message (from curl?) followed by json response)
'''
- headerPrep = [k + ':' + headers[k] for k in headers.keys()]
- cmd = 'curl -H "x-access-token:' + self.apiKey + '" -H "Content-MD5:' + headers['Content-MD5'].strip() +'" -T "'+ transFile +'" -X PUT ' + resourcePath
- p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
- return p.stdout.read()
+ # headerPrep = [k + ':' + headers[k] for k in headers.keys()]
+ # cmd = 'curl -H "x-access-token:' + self.apiKey + '" -H "Content-MD5:' + headers['Content-MD5'].strip() +'" -T "'+ transFile +'" -X PUT ' + resourcePath
+ # p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
+ # output = p.stdout.read()
+ # print(output)
+ # return output
+ import requests
+ put_headers = {
+ 'Content-MD5' : headers['Content-MD5'].strip(),
+ 'x-access-token': self.apiKey
+ }
+ put_val = requests.put(resourcePath, data, headers=put_headers)
+ if put_val.status_code != 200:
+ raise ServerResponseException("Multi-part upload: Server return code %s with error %s" % (put_val.status_code, put_val.reason))
+ return put_val.text
def callAPI(self, resourcePath, method, queryParams, postData, headerParams=None, forcePost=False):
'''
Call a REST API and return the server response.
-
+
An access token header is automatically added.
If a Content-Type header isn't included, one will be added with 'application/json' (except for PUT and forcePost calls).
Query parameters with values of None aren't sent to the server.
Server errors are to be handled by the caller (returned response contains error codes/msgs).
-
+
:param resourcePath: the url to call, not including server address and api version
:param method: REST method, including GET, POST (and forcePost, see below), and PUT (DELETE not yet supported)
:param queryParams: dictionary of query parameters to be added to url, except for forcePost where they are added as 'postData'; not used for PUT calls
@@ -97,97 +101,100 @@ def callAPI(self, resourcePath, method, queryParams, postData, headerParams=None
if self.userAgent:
headers['User-Agent'] = self.userAgent
if headerParams:
- for param, value in headerParams.iteritems():
+ for param, value in six.iteritems(headerParams):
headers[param] = value
# specify the content type
- if not headers.has_key('Content-Type') and not method=='PUT' and not forcePost:
+ if not 'Content-Type' in headers and not method=='PUT' and not forcePost:
headers['Content-Type'] = 'application/json'
- # include access token in header
+ # include access token in header
headers['Authorization'] = 'Bearer ' + self.apiKey
-
+
data = None
if method == 'GET':
if queryParams:
# Need to remove None values, these should not be sent
sentQueryParams = {}
- for param, value in queryParams.iteritems():
+ for param, value in six.iteritems(queryParams):
if value != None:
sentQueryParams[param] = value
- url = url + '?' + urllib.urlencode(sentQueryParams)
- request = urllib2.Request(url=url, headers=headers)
+ url = url + '?' + urllib.parse.urlencode(sentQueryParams)
+ request = urllib.request.Request(url=url, headers=headers)
elif method in ['POST', 'PUT', 'DELETE']:
if queryParams:
# Need to remove None values, these should not be sent
sentQueryParams = {}
- for param, value in queryParams.iteritems():
+ for param, value in six.iteritems(queryParams):
if value != None:
sentQueryParams[param] = value
- forcePostUrl = url
- url = url + '?' + urllib.urlencode(sentQueryParams)
+ forcePostUrl = url
+ url = url + '?' + urllib.parse.urlencode(sentQueryParams)
data = postData
if data:
- if type(postData) not in [str, int, float, bool]:
+ if type(postData) not in [str, int, float, bool, bytes]:
data = json.dumps(postData)
if not forcePost:
- if data and not len(data):
+ if data and not len(data):
data='\n' # temp fix, in case is no data in the file, to prevent post request from failing
- request = urllib2.Request(url=url, headers=headers, data=data)#,timeout=self.timeout)
- else: # use pycurl to force a post call, even w/o data
+ if data and six.PY3:
+ if type(data) is str:
+ data = data.encode()
+ request = urllib.request.Request(url=url, headers=headers, data=data)#,timeout=self.timeout)
+ else:
response = self.__forcePostCall__(forcePostUrl, sentQueryParams, headers)
- if method in ['PUT', 'DELETE']: #urllib doesnt do put and delete, default to pycurl here
+ if method in ['PUT', 'DELETE']:
if method == 'DELETE':
- raise NotImplementedError("DELETE REST API calls aren't currently supported")
+ raise NotImplementedError('DELETE REST API calls aren\'t currently supported')
response = self.__putCall__(url, headers, data)
- response = response.split()[-1] # discard upload status msg (from curl put?)
+ response = response.split()[-1] # discard upload status msg (from curl put?)
else:
raise RestMethodException('Method ' + method + ' is not recognized.')
# Make the request
if not forcePost and not method in ['PUT', 'DELETE']: # the normal case
try:
- response = urllib2.urlopen(request, timeout=self.timeout).read()
- except urllib2.HTTPError as e:
- response = e.read() # treat http error as a response (handle in caller)
- except urllib2.URLError as e:
- raise ServerResponseException('URLError: ' + str(e))
+ response = urllib.request.urlopen(request, timeout=self.timeout).read().decode('utf-8')
+ except urllib.error.HTTPError as e:
+ response = e.read().decode('utf-8') # treat http error as a response (handle in caller)
+ except urllib.error.URLError as e:
+ raise ServerResponseException('URLError: ' + str(e))
try:
data = json.loads(response)
except ValueError as e:
raise ServerResponseException('Error decoding json in server response')
- return data
+ return data
def deserialize(self, obj, objClass):
"""
Deserialize a JSON string into a BaseSpacePy object.
:param obj: A dictionary (or object?) to be deserialized into a class (objClass); or a value to be passed into a new native python type (objClass)
- :param objClass: A class object or native python type for the deserialized object, or a string of a class name or native python type. (eg, Project.Project, int, 'Project', 'int')
+ :param objClass: A class object or native python type for the deserialized object, or a string of a class name or native python type. (eg, Project.Project, int, 'Project', 'int')
:returns: A deserialized object
- """
+ """
# Create an object class from objClass, if a string was passed in
# Avoid native python types 'file'
- if type(objClass) == str:
+ if type(objClass) == str:
try:
- if (not str(objClass)=='File'):
+ if (not str(objClass)=='File'):
objClass = eval(objClass.lower())
else:
objClass = eval(objClass + '.' + objClass)
except NameError: # not a native type, must be model class
objClass = eval(objClass + '.' + objClass)
-
+
# Create an instance of the object class
- # If the instance is a native python type, return it
+ # If the instance is a native python type, return it
if objClass in [str, int, float, bool]:
return objClass(obj)
instance = objClass()
-
+
# For every swaggerType in the instance that is also in the passed-in obj,
# set the instance value for native python types,
# or recursively deserialize class instances.
# For dynamic types, substitute real class after looking up 'Type' value.
# For lists, deserialize all members of a list, including lists of lists (though not list of list of list...).
- # For datetimes, convert to a readable output string
- for attr, attrType in instance.swaggerTypes.iteritems():
+ # For datetimes, convert to a readable output string
+ for attr, attrType in six.iteritems(instance.swaggerTypes):
if attr in obj:
value = obj[attr]
if attrType in ['str', 'int', 'float', 'bool']:
@@ -195,52 +202,52 @@ def deserialize(self, obj, objClass):
try:
value = attrType(value)
except UnicodeEncodeError:
- value = unicode(value)
- setattr(instance, attr, value)
- elif attrType == 'DynamicType':
+ value = six.text_type(value)
+ setattr(instance, attr, value)
+ elif attrType == 'DynamicType':
try:
- model_name = instance._dynamicType[value['Type']]
+ model_name = instance._dynamicType[value['Type']]
except KeyError:
pass
# suppress this warning, which is caused by a bug in BaseSpace
- #warn("Warning - unrecognized dynamic type: " + value['Type'])
+ #warn("Warning - unrecognized dynamic type: " + value['Type'])
else:
setattr(instance, attr, self.deserialize(value, model_name))
elif 'list<' in attrType:
match = re.match('list<(.*)>', attrType)
- subClass = match.group(1)
- subValues = []
+ subClass = match.group(1)
+ subValues = []
# lists of dynamic type
- if subClass == 'DynamicType':
- for subValue in value:
+ if subClass == 'DynamicType':
+ for subValue in value:
try:
- new_type = instance._dynamicType[subValue['Type']]
+ new_type = instance._dynamicType[subValue['Type']]
except KeyError:
- pass
+ pass
# suppress this warning, which is caused by a bug in BaseSpace
- #warn("Warning - unrecognized (list of) dynamic types: " + subValue['Type'])
+ #warn("Warning - unrecognized (list of) dynamic types: " + subValue['Type'])
else:
- subValues.append(self.deserialize(subValue, new_type))
+ subValues.append(self.deserialize(subValue, new_type))
setattr(instance, attr, subValues)
# typical lists
- else:
+ else:
for subValue in value:
subValues.append(self.deserialize(subValue, subClass))
setattr(instance, attr, subValues)
# list of lists (e.g. map[] property type)
elif 'listoflists<' in attrType:
match = re.match('listoflists<(.*)>', attrType)
- subClass = match.group(1)
- outvals = []
+ subClass = match.group(1)
+ outvals = []
for outval in value:
invals = []
for inval in outval:
invals.append(self.deserialize(inval, subClass))
outvals.append(invals)
setattr(instance, attr, outvals)
-
- elif attrType=='dict':
+
+ elif attrType=='dict':
setattr(instance, attr, value)
elif attrType=='datetime':
dt = dateutil.parser.parse(value)
diff --git a/src/BaseSpacePy/api/AppLaunchHelpers.py b/src/BaseSpacePy/api/AppLaunchHelpers.py
index 2db5944..6dd6b4d 100644
--- a/src/BaseSpacePy/api/AppLaunchHelpers.py
+++ b/src/BaseSpacePy/api/AppLaunchHelpers.py
@@ -19,22 +19,13 @@
import os
from BaseMountInterface import BaseMountInterface
-from BaseSpaceAPI import BaseSpaceAPI
-
-api = BaseSpaceAPI()
-API_VERSION = api.version
-
-SKIP_PROPERTIES = ["app-session-name"]
+from BaseSpaceException import ServerResponseException
# if these strings are in the property names, we should not try to capture default values for them.
+# these are "global" but are needed by more than one object, so it's the cleanest way for now
BS_ENTITIES = ["sample", "project", "appresult", "file"]
BS_ENTITY_LIST_NAMES = ["Samples", "Projects", "AppResults", "Files"]
-LAUNCH_HEADER = {
- "StatusSummary": "AutoLaunch",
- "AutoStart": True,
-}
-
class AppSessionMetaData(object):
"""
@@ -47,6 +38,8 @@ class AppSessionMetaData(object):
__metaclass__ = abc.ABCMeta
+ SKIP_PROPERTIES = ["app-session-name", "attributes", "columns", "num-columns", "rowcount", "IsMultiNode"]
+
def __init__(self, appsession_metadata):
"""
@@ -54,7 +47,57 @@ def __init__(self, appsession_metadata):
"""
self.asm = appsession_metadata
+ def _get_all_duplicate_names(self):
+ appsession_properties = self.get_properties()
+ all_names = set()
+ duplicate_names = set()
+ for as_property in appsession_properties:
+ property_name = str(self.unpack_bs_property(as_property, "Name"))
+ if not property_name.startswith("Input"):
+ continue
+ property_basename = property_name.split(".")[-1]
+ if property_basename in self.SKIP_PROPERTIES:
+ continue
+ if property_basename in BS_ENTITY_LIST_NAMES:
+ continue
+ if property_basename in all_names:
+ duplicate_names.add(property_basename)
+ else:
+ all_names.add(property_basename)
+ return duplicate_names
+
+ def _get_map_underlying_types(self, content):
+ """
+ Takes the content present in an existing appsession map type
+ and converts this into the underlying columns with their name and type
+ :param content: the raw content of the appsession we are deriving from
+ :return: a list of generic columns with name and type
+ """
+ # this should return a list of strings for a single row of the raw table
+ # this only gets us the names - we need to look up types later :(
+ first_row = content[0]
+ columns = [".".join(column.split(".")[:-1]) for column in self.unpack_bs_property(first_row, "Values")]
+ return columns
+
+ @staticmethod
+ def _get_owning_map(all_properties, property_name):
+ # look for the property name without its suffix, since for an underlying property this will be eg. "row1"
+ property_name = ".".join(property_name.split(".")[:-1])
+ for property_ in all_properties:
+ if property_["Type"] == "map":
+ if property_name in property_["ColumnTypes"]:
+ return property_
+ # if we didn't find a map with this property, return None
+ return None
+
def get_refined_appsession_properties(self):
+ """
+ Unpacks the properties from an appsession and refines them ready to make a launch specification
+
+ :return: properties (list of dict of "Name" and "Type")
+ defaults (dict from property name to default value)
+ """
+ all_names = self._get_all_duplicate_names()
appsession_properties = self.get_properties()
properties = []
defaults = {}
@@ -63,10 +106,10 @@ def get_refined_appsession_properties(self):
property_type = str(self.unpack_bs_property(as_property, "Type"))
if not property_name.startswith("Input"):
continue
- if property_name.count(".") != 1:
- continue
property_basename = property_name.split(".")[-1]
- if property_basename in SKIP_PROPERTIES:
+ if property_basename in all_names:
+ property_basename = ".".join(property_name.split(".")[-2:])
+ if property_basename in self.SKIP_PROPERTIES:
continue
if property_basename in BS_ENTITY_LIST_NAMES:
continue
@@ -74,6 +117,28 @@ def get_refined_appsession_properties(self):
"Name": property_name,
"Type": property_type,
}
+ # this sets up the map, but we need to see examples of the columns in other properties to get the types
+ if property_type == "map":
+ content = self.unpack_bs_property(as_property, "Content")
+ columns = self._get_map_underlying_types(content)
+ this_property["ColumnNames"] = columns
+ # set a dict with an empty target that will be filled in later
+ this_property["ColumnTypes"] = dict((column, None) for column in columns)
+ properties.append(this_property)
+ continue
+ # check to see whether this property is part of an existing map property
+ owning_map = self._get_owning_map(properties, property_name)
+ if owning_map:
+ # if this property is part of a map, the last part of the name will be its row
+ # eg. Input.sample-experiments.happy-id.row1
+ # we should remove that suffix
+ property_name = ".".join(property_name.split(".")[:-1])
+ if owning_map["ColumnTypes"][property_name]:
+ assert owning_map["ColumnTypes"][property_name] == property_type
+ else:
+ owning_map["ColumnTypes"][property_name] = property_type
+ # if we are just using the property to grab types for a map, we shouldn't record it anywhere else
+ continue
properties.append(this_property)
bald_type = property_type.translate(None, "[]")
if bald_type in BS_ENTITIES:
@@ -132,6 +197,9 @@ def get_app_name(self):
def get_app_id(self):
return self.asm.Application.Id
+ def get_app_version(self):
+ return self.asm.Application.VersionNumber
+
@staticmethod
def unpack_bs_property(bs_property, attribute):
return getattr(bs_property, attribute)
@@ -147,6 +215,9 @@ def get_app_name(self):
def get_app_id(self):
return self.asm["Response"]["Application"]["Id"]
+ def get_app_version(self):
+ return self.asm["Response"]["Application"]["VersionNumber"]
+
@staticmethod
def unpack_bs_property(bs_property, attribute):
return bs_property[attribute]
@@ -161,21 +232,84 @@ class LaunchSpecification(object):
Class to help work with a BaseSpace app launch specification, which includes the properties and any defaults
"""
+ LAUNCH_HEADER = {
+ "StatusSummary": "AutoLaunch",
+ "AutoStart": True,
+ }
+
def __init__(self, properties, defaults):
self.properties = properties
+ self.cleaned_names = {}
self.property_lookup = dict((self.clean_name(property_["Name"]), property_) for property_ in self.properties)
self.defaults = defaults
- @staticmethod
- def clean_name(parameter_name):
+ def get_map_underlying_names_by_type(self, map_name, underlying_type):
+ map_property = self.property_lookup[map_name]
+ return [varname for varname, vartype in map_property["ColumnTypes"].iteritems() if vartype == underlying_type]
+
+ def get_map_position_by_underlying_name(self, map_name, underlying_name):
+ map_property = self.property_lookup[map_name]
+ return map_property["ColumnNames"].index(underlying_name)
+
+ def get_map_underlying_name_and_type_by_position(self, map_name, position):
+ map_property = self.property_lookup[map_name]
+ underlying_name = map_property["ColumnNames"][position]
+ underlying_type = map_property["ColumnTypes"][underlying_name]
+ return underlying_name, underlying_type
+
+ def get_map_underlying_types(self, map_name):
+ map_property = self.property_lookup[map_name]
+ return map_property["ColumnTypes"].values()
+
+ def clean_name(self, parameter_name):
"""
strip off the Input. prefix, which is needed by the launch payload but gets in the way otherwise
:param parameter_name: parameter name to clean
:return: cleaned name
"""
- prefix, cleaned_name = parameter_name.split(".")
- assert prefix == "Input"
- return cleaned_name
+ if not self.cleaned_names:
+ dup_names = set()
+ all_names = set()
+ for property_ in self.properties:
+ split_name = property_["Name"].split(".")
+ name_prefix = split_name[0]
+ assert name_prefix == "Input"
+ name_suffix = split_name[-1]
+ if name_suffix in all_names:
+ dup_names.add(name_suffix)
+ else:
+ all_names.add(name_suffix)
+ for property_ in self.properties:
+ full_name = property_["Name"]
+ split_name = full_name.split(".")
+ name_suffix = split_name[-1]
+ if name_suffix in dup_names:
+ clean_name = ".".join(split_name[-2:])
+ else:
+ clean_name = split_name[-1]
+ self.cleaned_names[full_name] = clean_name
+ return self.cleaned_names[parameter_name]
+
+ def process_parameter(self, param, varname):
+ # if option is prefixed with an @, it's a file (or process substitution with <() )
+ # so we should read inputs from there
+ # for properties with type map this is probably going to be prone to error :(
+ property_type = self.get_property_bald_type(varname)
+ if param.startswith("@") and property_type != "string":
+ assert self.is_list_property(varname) or property_type == "map", "cannot specify non-list parameter with file"
+ with open(param[1:]) as fh:
+ if property_type == "map":
+ processed_param = [line.strip().split(",") for line in fh]
+ else:
+ processed_param = [line.strip() for line in fh]
+ else:
+ if self.is_list_property(varname):
+ processed_param = param.split(",")
+ elif property_type == "map":
+ processed_param = [row.split(",") for row in param.split("::")]
+ else:
+ processed_param = param
+ return processed_param
def resolve_list_variables(self, var_dict):
"""
@@ -189,7 +323,8 @@ def resolve_list_variables(self, var_dict):
if self.is_list_property(varname) and not isinstance(varval, list):
var_dict[varname] = [varval]
# raise AppLaunchException("non-list property specified for list parameter")
- if not self.is_list_property(varname) and isinstance(varval, list):
+ # if they've supplied a list, it must be for a list or map property
+ if (not self.is_list_property(varname) and self.get_property_type(varname) != "map") and isinstance(varval, list):
raise LaunchSpecificationException("list property specified for non-list parameter")
@staticmethod
@@ -240,7 +375,7 @@ def make_sample_attribute_entry(sampleid, wrapped_sampleid, sample_attributes):
this_sample_attributes.append(attribute_entry)
return this_sample_attributes
- def populate_properties(self, var_dict, sample_attributes={}):
+ def populate_properties(self, var_dict, api_version, sample_attributes={}):
"""
Uses the base properties of the object and an instantiation of those properties (var_dict)
build a dictionary that represents the launch payload
@@ -258,7 +393,7 @@ def populate_properties(self, var_dict, sample_attributes={}):
all_sample_attributes = {
"Type": "map[]",
"Name": "Input.sample-id.attributes",
- "items": []
+ "itemsmap": []
}
for property_ in populated_properties:
property_name = self.clean_name(property_["Name"])
@@ -266,30 +401,56 @@ def populate_properties(self, var_dict, sample_attributes={}):
bald_type = str(property_type).translate(None, "[]")
property_value = var_dict[property_name]
processed_value = ""
+ map_properties = []
if bald_type in BS_ENTITIES:
if "[]" in property_type:
processed_value = []
for one_val in property_value:
- wrapped_value = "%s/%ss/%s" % (API_VERSION, bald_type, one_val)
+ wrapped_value = "%s/%ss/%s" % (api_version, bald_type, one_val)
processed_value.append(wrapped_value)
if sample_attributes and bald_type == "sample":
one_sample_attributes = self.make_sample_attribute_entry(one_val, wrapped_value,
sample_attributes)
- all_sample_attributes["items"].append(one_sample_attributes)
+ all_sample_attributes["itemsmap"].append(one_sample_attributes)
else:
- processed_value = "%s/%ss/%s" % (API_VERSION, bald_type, property_value)
+ processed_value = "%s/%ss/%s" % (api_version, bald_type, property_value)
if sample_attributes and bald_type == "sample":
one_sample_attributes = self.make_sample_attribute_entry(property_value, processed_value,
sample_attributes)
sample_attributes["Items"].append(one_sample_attributes)
+ if bald_type == "map":
+ # for each argument, create one entry in the property list for each column
+ for rownum, row in enumerate(property_value):
+ for colnum, value in enumerate(row):
+ underlying_name, underlying_type = self.get_map_underlying_name_and_type_by_position(property_name, colnum)
+ if underlying_type in BS_ENTITIES:
+ wrapped_value = "%s/%ss/%s" % (api_version, underlying_type, value)
+ else:
+ wrapped_value = value
+ assembled_args = {
+ "Type" : underlying_type,
+ "Name" : "%s.row%d" % (underlying_name, rownum+1),
+ "Content" : wrapped_value
+ }
+ map_properties.append(assembled_args)
+ rowcount_entry = {
+ "Type" : "string",
+ "Name" : "Input.%s.rowcount" % (property_name),
+ "Content" : len(property_value)
+ }
+ map_properties.append(rowcount_entry)
+ # also create an entry for the number of columns
if not processed_value:
processed_value = property_value
if "[]" in property_type:
property_["items"] = processed_value
else:
property_["Content"] = processed_value
+ populated_properties.extend(map_properties)
if sample_attributes:
populated_properties.append(all_sample_attributes)
+ # remove map properties, which aren't needed for launch
+ populated_properties = [property_ for property_ in populated_properties if property_["Type"] != "map"]
return populated_properties
def get_variable_requirements(self):
@@ -328,6 +489,11 @@ def get_property_bald_type(self, property_name):
"""
return str(self.get_property_type(property_name)).translate(None, "[]")
+ def get_underlying_map_type(self, map_property_name, position):
+ map_property = self.property_lookup[map_property_name]
+ underlying_property_name = map_property["ColumnNames"][position]
+ return map_property["ColumnTypes"][underlying_property_name]
+
def is_list_property(self, property_name):
"""
is a given property a list property
@@ -343,7 +509,7 @@ def count_list_properties(self):
"""
return [self.is_list_property(property_name) for property_name in self.get_minimum_requirements()].count(True)
- def make_launch_json(self, user_supplied_vars, launch_name, sample_attributes={}, agent_id=""):
+ def make_launch_json(self, user_supplied_vars, launch_name, api_version, sample_attributes={}, agent_id=""):
"""
build the launch payload (a json blob as a string) based on the supplied mapping from property name to value
@@ -355,7 +521,7 @@ def make_launch_json(self, user_supplied_vars, launch_name, sample_attributes={}
:param agent_id: an AgentId to be attached to the launch, if specifed
"""
# build basic headers
- launch_dict = copy.copy(LAUNCH_HEADER)
+ launch_dict = copy.copy(self.LAUNCH_HEADER)
launch_dict["Name"] = launch_name
if agent_id:
launch_dict["AgentId"] = agent_id
@@ -364,18 +530,16 @@ def make_launch_json(self, user_supplied_vars, launch_name, sample_attributes={}
if required_vars - supplied_var_names:
raise LaunchSpecificationException(
"Compulsory variable(s) missing! (%s)" % str(required_vars - supplied_var_names))
- if supplied_var_names - (self.get_variable_requirements() | {"LaunchName"}):
- print "warning! unused variable(s) specified: (%s)" % str(
- supplied_var_names - self.get_variable_requirements())
+ if supplied_var_names - self.get_variable_requirements():
+ print("warning! unused variable(s) specified: (%s)" % str(supplied_var_names - self.get_variable_requirements()))
all_vars = copy.copy(self.defaults)
all_vars.update(user_supplied_vars)
self.resolve_list_variables(all_vars)
- properties_dict = self.populate_properties(all_vars, sample_attributes)
+ properties_dict = self.populate_properties(all_vars, api_version, sample_attributes)
launch_dict["Properties"] = properties_dict
return json.dumps(launch_dict)
- def format_property_information(self):
- lines = ["\t".join(["Name", "Type", "Default"])]
+ def property_information_generator(self):
minimum_requirements = self.get_minimum_requirements()
for property_ in sorted(self.properties):
property_name = self.clean_name(property_["Name"])
@@ -385,20 +549,34 @@ def format_property_information(self):
output = [property_name, property_type]
if property_name in self.defaults:
output.append(str(self.defaults[property_name]))
- lines.append("\t".join(output))
- return "\n".join(lines)
+ yield output
+
+ def format_property_information(self):
+ header = ["\t".join(["Name", "Type", "Default"])]
+ return "\n".join(header + ["\t".join(line) for line in self.property_information_generator()])
+
+ def format_map_types(self, property_name):
+ return ",".join(self.get_map_underlying_types(property_name))
def dump_property_information(self):
"""
dump all properties with their type and any default value
for verbose usage information output
"""
- print self.format_property_information()
+ print(self.format_property_information())
def format_minimum_requirements(self):
minimum_requirements = self.get_minimum_requirements()
- description = ["%s (%s)" % (varname, self.get_property_type(varname)) for varname in minimum_requirements]
- return " ".join(description)
+ descriptions = []
+ for varname in minimum_requirements:
+ property_type = self.get_property_type(varname)
+ if property_type == "map":
+ description = "%s (%s[%s])" % (varname, property_type, self.format_map_types(varname))
+ else:
+ description = "%s (%s)" % (varname, property_type)
+ descriptions.append(description)
+ return " ".join(descriptions)
+
class LaunchPayload(object):
"""
@@ -409,24 +587,74 @@ class LaunchPayload(object):
and mapping BaseMount paths to the API reference strings the launch needs
"""
- LAUNCH_NAME = "LaunchName"
+ ENTITY_TYPE_TO_METHOD_NAME = {
+ "sample": "getSampleById",
+ "appresult": "getAppResultById",
+ "project": "getProjectById",
+ "file": "getFileById"
+ }
- def __init__(self, launch_spec, args, configoptions):
+ def __init__(self, launch_spec, args, configoptions, api, disable_consistency_checking=True):
"""
:param launch_spec (LaunchSpecification)
:param args (list) list or arguments to the app launch. These could be BaseSpace IDs or BaseMount paths
:param configoptions (dict) key->value mapping for additional option values, such as genome-id
- the ordering of args has to match the ordering of the sorted minimum requirements
+ :param api (BaseSpaceAPI) BaseSpace API object
+ :param disable_consistency_checking (bool) default behaviour is to ensure all entities and the launch itself
+ is done with the same access token. This parameter allows inconsistency (at user's risk!)
+ the ordering of args has to match the ordering of the sorted minimum requirements from the launch_spec
It might be better to use a dict, but ultimately call order has to match at some point
"""
self._launch_spec = launch_spec
self._args = args
self._configoptions = configoptions
+ self._api = api
+ self._access_token = None if disable_consistency_checking else api.apiClient.apiKey
+ varnames = self._launch_spec.get_minimum_requirements()
+ if len(varnames) != len(self._args):
+ raise LaunchSpecificationException("Number of arguments does not match specification")
+
+ def _arg_entry_to_name(self, entry, entity_type):
+ # if the argument contains a path separator, it must be a valid BaseMount path
+ # otherwise, an exception will be raised by BaseMountInterface
+ if os.path.sep in entry:
+ bmi = BaseMountInterface(entry)
+ return bmi.name
+ # if this is not a BaseMount path, try to resolve an entity name using the API
+ # note we're relying on the regular naming of the API to provide the right method name
+ # if this throws an exception, it's up to the caller to catch and handle
+ entry = entry.strip('"')
+ method = getattr(self._api, self.ENTITY_TYPE_TO_METHOD_NAME[entity_type])
+ return method(entry).Name
def _find_all_entity_names(self, entity_type):
"""
get all the entity names for a particular entity type
used to make useful launch names
+
+ doing this for map types is pretty complicated. Imagine a map type defined like this:
+
+ {
+ "Name": "Input.sample-experiments",
+ "Type": "map",
+ "ColumnTypes": {
+ "sample-experiments.happy-id": "appresult",
+ "sample-experiments.result-label": "string"
+ },
+ "ColumnNames": [
+ "sample-experiments.happy-id",
+ "sample-experiments.result-label"
+ ]
+ }
+
+ and a map call like this:
+
+ [ [ "124124", "first" ] , [ "124127", "second" ] ]
+
+ then we want to look up the entity names of "124124" and "124127" based on their types
+ we get the type by looking up the variable in the position where the map call has been made
+ and then using that type definition to see where we can find the appropriate IDs in the underlying map
+
:param entity_type: the entity type to look for
:return: list of entity names
"""
@@ -434,13 +662,34 @@ def _find_all_entity_names(self, entity_type):
varnames = self._launch_spec.get_minimum_requirements()
for i, varname in enumerate(varnames):
arg = self._args[i]
- if self._launch_spec.get_property_bald_type(varname) == entity_type:
+ this_type = self._launch_spec.get_property_bald_type(varname)
+ if this_type == entity_type:
if not self._launch_spec.is_list_property(varname):
arg = [arg]
for entry in arg:
- if os.path.exists(entry):
- bmi = BaseMountInterface(entry)
- entity_names.append(bmi.name)
+ try:
+ name = self._arg_entry_to_name(entry, this_type)
+ entity_names.append(name)
+ # if we were unable to find a name, just press on
+ except (AttributeError, ServerResponseException):
+ pass
+ if this_type == "map":
+ # from the type we're after, get the variable name.
+ underlying_names = self._launch_spec.get_map_underlying_names_by_type(varname, entity_type)
+ # Use this to get the parameter positions.
+ varpositions = [self._launch_spec.get_map_position_by_underlying_name(varname, underlying_name)
+ for underlying_name in underlying_names]
+ # Use this to get the specifics for this call
+ for entry in arg:
+ for position in varpositions:
+ try:
+ name = self._arg_entry_to_name(entry[position], entity_type)
+ entity_names.append(name)
+ # if we were unable to find a name, just press on
+ except (AttributeError, ServerResponseException):
+ pass
+
+
return entity_names
def derive_launch_name(self, app_name):
@@ -449,51 +698,65 @@ def derive_launch_name(self, app_name):
:param app_name: name of app
:return: useful name for app launch
"""
- if self.LAUNCH_NAME in self._configoptions:
- return self._configoptions[self.LAUNCH_NAME]
+ launch_names = []
+ # maybe this should be down in the called function :/
+ for entity_type in [ "sample", "appresult", "file"]:
+ these_names = self._find_all_entity_names(entity_type)
+ launch_names.extend(these_names)
+ if len(launch_names) > 3:
+ contracted_names = launch_names[:3] + ["%dmore" % (len(launch_names) - 3)]
+ launch_instance_name = "+".join(contracted_names)
else:
- launch_names = self._find_all_entity_names("sample")
- if not launch_names:
- launch_names = self._find_all_entity_names("appresult")
- if len(launch_names) > 3:
- contracted_names = launch_names[:3] + ["%dmore" % (len(launch_names) - 3)]
- launch_instance_name = "+".join(contracted_names)
- else:
- launch_instance_name = "+".join(launch_names)
- return "%s : %s" % (app_name, launch_instance_name)
+ launch_instance_name = "+".join(launch_names)
+ return "%s : %s" % (app_name, launch_instance_name)
def is_valid_basespace_id(self, varname, basespace_id):
"""
This is not really needed if users are specifying inputs as BaseMount paths,
because in these cases validation happens elsewhere
-
- To validate other kinds of ID, we should (TODO!) resolve the type based on the varname
- and use the SDK to look it up.
"""
- return True
+ vartype = self._launch_spec.get_property_bald_type(varname)
+ flat_vartype = vartype.lower()
+ if flat_vartype in self.ENTITY_TYPE_TO_METHOD_NAME:
+ method = getattr(self._api, self.ENTITY_TYPE_TO_METHOD_NAME[flat_vartype])
+ method(basespace_id)
+ else:
+ return True
- def to_basespace_id(self, param_name, varval):
+ def preprocess_arg(self, param_type, varval):
"""
Checks if a value for a parameter looks like a BaseMount path and tries to convert it into a BaseSpace ID
- :param param_name: name of the parameter
+ :param param_type: type of the parameter
:param varval: value of parameter
:return basespaceid
"""
- if varval.startswith("/") and not os.path.exists(varval):
- raise LaunchSpecificationException("Parameter looks like a path, but does not exist: %s" % varval)
- if os.path.exists(varval):
+ if param_type == "string":
+ return varval
+ if os.path.sep in varval:
+ # if the argument contains a path separator, it must be a valid BaseMount path
+ # otherwise, an exception will be raised by BaseMountInterface
bmi = BaseMountInterface(varval)
- spec_type = self._launch_spec.get_property_bald_type(param_name)
+ # make sure we have a BaseMount access token to compare - old versions won't have one
+ # also make sure we've been passed an access token -
+ # if we haven't, access token consistency checking has been disabled.
+ if bmi.access_token and self._access_token and bmi.access_token != self._access_token:
+ raise LaunchSpecificationException(
+ "Access tokens between launch configuration and referenced BaseMount path do not match: %s" % varval)
basemount_type = bmi.type
- if spec_type != basemount_type:
+ if param_type != basemount_type:
raise LaunchSpecificationException(
- "wrong type of BaseMount path selected: %s needs to be of type %s" % (varval, spec_type))
+ "wrong type of BaseMount path selected: %s needs to be of type %s" % (varval, param_type))
bid = bmi.id
else:
- bid = varval
- assert self.is_valid_basespace_id(param_name, bid)
+ # strip off quotes, which will be what comes in from bs list samples -f csv
+ bid = varval.strip('"')
+ # skip this step for now - it could be really expensive for big launches
+ # try:
+ # self.is_valid_basespace_id(param_name, bid)
+ # except ServerResponseException as e:
+ # raise LaunchSpecificationException("invalid BaseSpace ID '%s' for var: %s (%s)" % (varval, param_name, str(e)))
return bid
def get_args(self):
@@ -506,9 +769,26 @@ def get_args(self):
for i, param_name in enumerate(params):
arg = self._args[i]
if isinstance(arg, list):
- arg_map[param_name] = [self.to_basespace_id(param_name, arg_part) for arg_part in arg]
+ if isinstance(arg[0], list):
+ preprocessed_rows = []
+ # for each row
+ for row in arg:
+ # for each column
+ preprocessed_row = []
+ for position, column_value in enumerate(row):
+ # look up type
+ underlying_type = self._launch_spec.get_underlying_map_type(param_name, position)
+ # convert
+ preprocessed_arg = self.preprocess_arg(underlying_type, column_value)
+ preprocessed_row.append(preprocessed_arg)
+ preprocessed_rows.append(preprocessed_row)
+ arg_map[param_name] = preprocessed_rows
+ else:
+ param_type = self._launch_spec.get_property_bald_type(param_name)
+ arg_map[param_name] = [self.preprocess_arg(param_type, arg_part) for arg_part in arg]
else:
- arg_map[param_name] = self.to_basespace_id(param_name, arg)
+ param_type = self._launch_spec.get_property_bald_type(param_name)
+ arg_map[param_name] = self.preprocess_arg(param_type, arg)
return arg_map
def get_all_variables(self):
diff --git a/src/BaseSpacePy/api/AuthenticationAPI.py b/src/BaseSpacePy/api/AuthenticationAPI.py
new file mode 100644
index 0000000..a0101f1
--- /dev/null
+++ b/src/BaseSpacePy/api/AuthenticationAPI.py
@@ -0,0 +1,169 @@
+import sys
+import time
+import getpass
+import os
+import requests
+
+# this tries to clean up the output at the expense of letting the user know they're in an insecure context...
+try:
+ requests.packages.urllib3.disable_warnings()
+except:
+ pass
+import logging
+
+import six
+
+from six.moves import input
+
+from six.moves import configparser
+
+
+logging.getLogger("requests").setLevel(logging.WARNING)
+
+__author__ = 'psaffrey'
+
+"""
+Objects to help with creating config files that contain the right details to be used by the BaseSpaceSDK
+
+One way uses the OAuth flow for web application authentication:
+
+https://developer.basespace.illumina.com/docs/content/documentation/authentication/obtaining-access-tokens
+
+to get an access token and put it in the proper place.
+
+Also partly available here is obtaining session tokens (cookies), although these are not currently used.
+"""
+
+class AuthenticationException(Exception):
+ pass
+
+class AuthenticationScopeException(AuthenticationException):
+ pass
+
+class AuthenticationAPI(object):
+ DEFAULT_CONFIG_NAME = "DEFAULT"
+
+ def __init__(self, config_path, api_server):
+ self.config_path = config_path
+ self.api_server = api_server
+ self.config = None
+ self.parse_config()
+
+ def parse_config(self):
+ """
+ parses the config_path or creates it if it doesn't exist
+
+ :param config_path: path to config file
+ :return: configparser object
+ """
+ self.config = configparser.SafeConfigParser()
+ self.config.optionxform = str
+ if os.path.exists(self.config_path):
+ self.config.read(self.config_path)
+
+ def construct_default_config(self, api_server):
+ self.config.set(self.DEFAULT_CONFIG_NAME, "apiServer", api_server)
+
+ def write_config(self):
+ with open(self.config_path, "w") as fh:
+ self.config.write(fh)
+
+
+######
+# the BaseSpaceAPI doesn't support using the session tokens (cookies) at the moment
+# but this is here in case it's useful to somebody :)
+class SessionAuthentication(AuthenticationAPI):
+ SESSION_AUTH_URI = "https://accounts.illumina.com/"
+ SESSION_TOKEN_NAME = "sessionToken"
+ COOKIE_NAME = "IComLogin"
+
+ def basespace_session(self, username, password):
+ s = requests.session()
+ payload = {"UserName": username,
+ "Password": password,
+ "ReturnUrl": "http://developer.basespace.illumina.com/dashboard"}
+ r = s.post(url=self.SESSION_AUTH_URI,
+ params={'Service': 'basespace'},
+ data=payload,
+ headers={'Content-Type': "application/x-www-form-urlencoded"},
+ allow_redirects=False)
+ return s, r
+
+ def check_session_details(self):
+ pass
+
+ def set_session_details(self, config_path):
+ inputfunc = raw_input if six.PY2 else input
+ username = inputfunc("username:")
+ password = getpass.getpass()
+ s, r = self.basespace_session(username, password)
+ self.config.set(self.DEFAULT_CONFIG_NAME, self.SESSION_TOKEN_NAME, r.cookies[self.COOKIE_NAME])
+ self.write_config()
+
+class OAuthAuthentication(AuthenticationAPI):
+ WAIT_TIME = 5.0
+ ACCESS_TOKEN_NAME = "accessToken"
+
+ def __init__(self, config_path, api_server, api_version):
+ super(OAuthAuthentication, self).__init__(config_path, api_server)
+ self.api_version = api_version
+
+ def set_oauth_details(self, client_id, client_secret, scopes):
+ scope_str = ",".join(scopes)
+ OAUTH_URI = "%s%s/oauthv2/deviceauthorization" % (self.api_server, self.api_version)
+ TOKEN_URI = "%s%s/oauthv2/token" % (self.api_server, self.api_version)
+ s = requests.session()
+ # make the initial request
+ auth_payload = {
+ "response_type": "device_code",
+ "client_id": client_id,
+ "scope": scope_str,
+ }
+ try:
+ r = s.post(url=OAUTH_URI,
+ data=auth_payload)
+ except Exception as e:
+ raise AuthenticationException("Failed to communicate with server: %s" % str(e))
+ # show the URL to the user
+ try:
+ payload = r.json()
+ except ValueError:
+ raise AuthenticationException("bad payload from server - perhaps you should use https instead of http?")
+ if 'error' in payload:
+ if payload['error'] == 'invalid_scope':
+ raise AuthenticationScopeException("Authentication requested with invalid scope: %s" % scope_str)
+ else:
+ msg = payload['error_description'] if 'error_description' in payload else payload['error']
+ raise AuthenticationException(msg)
+ auth_url = payload["verification_with_code_uri"]
+ auth_code = payload["device_code"]
+ print("please authenticate here: %s" % auth_url)
+ # poll the token URL until we get the token
+ token_payload = {
+ "client_id": client_id,
+ "client_secret": client_secret,
+ "code": auth_code,
+ "grant_type": "device"
+ }
+ access_token = None
+ while 1:
+ # put the token into the config file
+ r = s.post(url=TOKEN_URI,
+ data=token_payload)
+ if r.status_code == 400:
+ if r.json()["error"] == "access_denied" or r.json()["error"] == "AccessDenied":
+ sys.stdout.write("\n")
+ break
+ sys.stdout.write(".")
+ sys.stdout.flush()
+ time.sleep(self.WAIT_TIME)
+ else:
+ sys.stdout.write("\n")
+ access_token = r.json()["access_token"]
+ break
+ self.construct_default_config(self.api_server)
+ if not access_token:
+ raise Exception("problem obtaining token!")
+ print("Success!")
+ self.config.set(self.DEFAULT_CONFIG_NAME, self.ACCESS_TOKEN_NAME, access_token)
+ self.write_config()
diff --git a/src/BaseSpacePy/api/BaseAPI.py b/src/BaseSpacePy/api/BaseAPI.py
index da2de39..33a8cab 100644
--- a/src/BaseSpacePy/api/BaseAPI.py
+++ b/src/BaseSpacePy/api/BaseAPI.py
@@ -1,10 +1,6 @@
from pprint import pprint
-import urllib2
import shutil
-import urllib
-import httplib
-import cStringIO
import json
import os
import inspect
@@ -12,6 +8,11 @@
from BaseSpacePy.api.APIClient import APIClient
from BaseSpacePy.api.BaseSpaceException import *
from BaseSpacePy.model import *
+from itertools import chain
+from six import moves
+from six.moves import urllib
+
+from six.moves import http_client
class BaseAPI(object):
@@ -19,11 +20,11 @@ class BaseAPI(object):
Parent class for BaseSpaceAPI and BillingAPI classes
'''
- def __init__(self, AccessToken, apiServerAndVersion, userAgent, timeout=10, verbose=False):
+ def __init__(self, AccessToken, apiServerAndVersion, userAgent=None, timeout=10, verbose=False):
'''
:param AccessToken: the current access token
:param apiServerAndVersion: the api server URL with api version
- :param timeout: (optional) the timeout in seconds for each request made, default 10
+ :param timeout: (optional) the timeout in seconds for each request made, default 10
:param verbose: (optional) prints verbose output, default False
'''
self.apiClient = APIClient(AccessToken, apiServerAndVersion, userAgent=userAgent, timeout=timeout)
@@ -33,17 +34,17 @@ def __json_print__(self, label, var):
try:
prefix = " " * len(label)
var_list = json.dumps(var,indent=4).split('\n') # ,ensure_ascii=False
- print label + var_list[0]
+ print(label + var_list[0])
if len(var_list)>1:
- print "\n".join( [prefix + s for s in var_list[1:]] )
+ print("\n".join( [prefix + s for s in var_list[1:]] ))
except UnicodeDecodeError:
- pass # we could disable ascii-enforcing, as shown above, but
+ pass # we could disable ascii-enforcing, as shown above, but
# this will massively increase the volume of logs
def __singleRequest__(self, myModel, resourcePath, method, queryParams, headerParams, postData=None, forcePost=False):
'''
Call a REST API and deserialize response into an object, handles errors from server.
-
+
:param myModel: a Response object that includes a 'Response' swaggerType key with a value for the model type to return
:param resourcePath: the api url path to call (without server and version)
:param method: the REST method type, eg. GET
@@ -51,99 +52,119 @@ def __singleRequest__(self, myModel, resourcePath, method, queryParams, headerPa
:param headerParams: a dictionary of header parameters
:param postData: (optional) data to POST, default None
:param version: (optional) print detailed output, default False
- :param forcePost: (optional) use a POST call with pycurl instead of urllib, default False (used only when POSTing with no post data?)
+ :param forcePost: (optional) use a POST call, default False (used only when POSTing with no post data?)
:raises ServerResponseException: if server returns an error or has no response
:returns: an instance of the Response model from the provided myModel
'''
- if self.verbose:
- print ""
- print "* " + inspect.stack()[1][3] + " (" + str(method) + ")" # caller
- print ' # Path: ' + str(resourcePath)
- print ' # QPars: ' + str(queryParams)
- print ' # Hdrs: ' + str(headerParams)
- print ' # forcePost: ' + str(forcePost)
+ if self.verbose:
+ print("")
+ print("* " + inspect.stack()[1][3] + " (" + str(method) + ")") # caller
+ print(' # Path: ' + str(resourcePath))
+ print(' # QPars: ' + str(queryParams))
+ print(' # Hdrs: ' + str(headerParams))
+ print(' # forcePost: ' + str(forcePost))
self.__json_print__(' # postData: ',postData)
response = self.apiClient.callAPI(resourcePath, method, queryParams, postData, headerParams, forcePost=forcePost)
if self.verbose:
self.__json_print__(' # Response: ',response)
- if not response:
+ if not response:
raise ServerResponseException('No response returned')
- if response.has_key('ResponseStatus'):
- if response['ResponseStatus'].has_key('ErrorCode'):
+ if 'ResponseStatus' in response:
+ if 'ErrorCode' in response['ResponseStatus']:
raise ServerResponseException(str(response['ResponseStatus']['ErrorCode'] + ": " + response['ResponseStatus']['Message']))
- elif response['ResponseStatus'].has_key('Message'):
+ elif 'Message' in response['ResponseStatus']:
raise ServerResponseException(str(response['ResponseStatus']['Message']))
- elif response.has_key('ErrorCode'):
+ elif 'ErrorCode' in response:
raise ServerResponseException(response["MessageFormatted"])
-
+
responseObject = self.apiClient.deserialize(response, myModel)
if hasattr(responseObject, "Response"):
return responseObject.Response
else:
return responseObject
- def __listRequest__(self, myModel, resourcePath, method, queryParams, headerParams):
+ def __listRequest__(self, myModel, resourcePath, method, queryParams, headerParams, sort=True):
'''
Call a REST API that returns a list and deserialize response into a list of objects of the provided model.
Handles errors from server.
+ Sorting by date for each call is the default, so that if a new item is created while we're paging through
+ we'll pick it up at the end. However, this should be switched off for some calls (like variants)
+
:param myModel: a Model type to return a list of
:param resourcePath: the api url path to call (without server and version)
:param method: the REST method type, eg. GET
:param queryParams: a dictionary of query parameters
:param headerParams: a dictionary of header parameters
+ :param sort: sort the outputs from the API to prevent race-conditions
- :raises ServerResponseException: if server returns an error or has no response
+ :raises ServerResponseException: if server returns an error or has no response
:returns: a list of instances of the provided model
'''
- if self.verbose:
- print ""
- print "* " + inspect.stack()[1][3] + " (" + str(method) + ")" # caller
- print ' # Path: ' + str(resourcePath)
- print ' # QPars: ' + str(queryParams)
- print ' # Hdrs: ' + str(headerParams)
- response = self.apiClient.callAPI(resourcePath, method, queryParams, None, headerParams)
if self.verbose:
- self.__json_print__(' # Response: ',response)
- if not response:
- raise ServerResponseException('No response returned')
- if response['ResponseStatus'].has_key('ErrorCode'):
- raise ServerResponseException(str(response['ResponseStatus']['ErrorCode'] + ": " + response['ResponseStatus']['Message']))
- elif response['ResponseStatus'].has_key('Message'):
- raise ServerResponseException(str(response['ResponseStatus']['Message']))
-
- respObj = self.apiClient.deserialize(response, ListResponse.ListResponse)
- return [self.apiClient.deserialize(c, myModel) for c in respObj._convertToObjectList()]
+ print("")
+ print("* " + inspect.stack()[1][3] + " (" + str(method) + ")") # caller
+ print(' # Path: ' + str(resourcePath))
+ print(' # QPars: ' + str(queryParams))
+ print(' # Hdrs: ' + str(headerParams))
+ number_received = 0
+ total_number = None
+ responses = []
+ # if the user explicitly sets a Limit in queryParams, just make one call with that limit
+ justOne = False
+ if "Limit" in queryParams:
+ justOne = True
+ else:
+ queryParams["Limit"] = 1024
+ if sort:
+ queryParams["SortBy"] = "DateCreated"
+ while total_number is None or number_received < total_number:
+ queryParams["Offset"] = number_received
+ response = self.apiClient.callAPI(resourcePath, method, queryParams, None, headerParams)
+ if self.verbose:
+ self.__json_print__(' # Response: ',response)
+ if not response:
+ raise ServerResponseException('No response returned')
+ if 'ErrorCode' in response['ResponseStatus']:
+ raise ServerResponseException(str(response['ResponseStatus']['ErrorCode'] + ": " + response['ResponseStatus']['Message']))
+ elif 'Message' in response['ResponseStatus']:
+ raise ServerResponseException(str(response['ResponseStatus']['Message']))
+
+ respObj = self.apiClient.deserialize(response, ListResponse.ListResponse)
+ responses.append(respObj)
+ if justOne:
+ break
+ # if a TotalCount is not an attribute, assume we have all of them (eg. variantsets)
+ if not hasattr(respObj.Response, "TotalCount"):
+ break
+ # allow the total number to change on each call
+ # to catch the race condition where a new entity appears while we're calling
+ total_number = respObj.Response.TotalCount
+ if total_number > 0 and respObj.Response.DisplayedCount == 0:
+ raise ServerResponseException("Paged query returned no results")
+ number_received += respObj.Response.DisplayedCount
+
+ return [self.apiClient.deserialize(c, myModel) for c in chain(*[ ro._convertToObjectList() for ro in responses ])]
def __makeCurlRequest__(self, data, url):
'''
Make a curl POST request
-
+
:param data: data to post (eg. list of tuples of form (key, value))
:param url: url to post data to
-
+
:raises ServerResponseException: if server returns an error or has no response
:returns: dictionary of api server response
'''
- # pycurl is hard to get working, so best to cauterise it into only the functions where it is needed
- import pycurl
- post = urllib.urlencode(data)
- response = cStringIO.StringIO()
- c = pycurl.Curl()
- c.setopt(pycurl.URL,url)
- c.setopt(pycurl.POST, 1)
- c.setopt(pycurl.POSTFIELDS, post)
- c.setopt(c.WRITEFUNCTION, response.write)
- c.perform()
- c.close()
- respVal = response.getvalue()
- if not respVal:
+ import requests
+ r = requests.post(url, data)
+ if not r:
raise ServerResponseException("No response from server")
- obj = json.loads(respVal)
- if obj.has_key('error'):
+ obj = json.loads(r.text)
+ if 'error' in obj:
raise ServerResponseException(str(obj['error'] + ": " + obj['error_description']))
- return obj
+ return obj
def getTimeout(self):
'''
@@ -154,21 +175,21 @@ def getTimeout(self):
def setTimeout(self, time):
'''
Specify the timeout in seconds for each request made
-
+
:param time: timeout in seconds
- '''
+ '''
self.apiClient.timeout = time
-
+
def getAccessToken(self):
'''
- Returns the current access token.
- '''
- return self.apiClient.apiKey
+ Returns the current access token.
+ '''
+ return self.apiClient.apiKey
def setAccessToken(self, token):
'''
Sets the current access token.
-
+
:param token: an access token
'''
- self.apiClient.apiKey = token
+ self.apiClient.apiKey = token
diff --git a/src/BaseSpacePy/api/BaseMountInterface.py b/src/BaseSpacePy/api/BaseMountInterface.py
index 74c8fc2..c929a4c 100644
--- a/src/BaseSpacePy/api/BaseMountInterface.py
+++ b/src/BaseSpacePy/api/BaseMountInterface.py
@@ -1,7 +1,7 @@
"""
class to wrap a directory mounted using BaseMount and provide convenience functions to get at the metadata stored there
-The currently supported metadata extraction uses the files created by metaBSFS, but is limited to first class entities like projects and samples
+The currently supported metadata extraction uses the files created by BaseMount, but is limited to first class entities like projects and samples
it will fail (and throw an exception) when pointing at other directories. For some of these it's not really clear what the behaviour *should* be
eg. ~/BaseSpace/current_user/Projects/Sloths\ Test/Samples/
which is the owning directory for the "Sloths Test" samples. Should this be a directory of type "project" and id of the "Hyperion Test" project?
@@ -22,18 +22,21 @@ class BaseMountInterface(object):
def __init__(self, path):
if path.endswith(os.sep):
path = path[:-1]
- self.path = path
+ self.path = os.path.expanduser(path)
self.id = None
self.type = None
+ self.access_token = None
self.name = os.path.basename(path)
if not self.__validate_basemount__():
- raise BaseMountInterfaceException("Path: %s does not seem to be a BaseMount path" % self.path)
+ raise BaseMountInterfaceException("Path: %s does not seem to be a BaseMount entity path" % self.path)
self.__resolve_details__()
def __validate_basemount__(self):
"""
- Checks whether the chosen directory is a BSFS directory
+ Checks whether the chosen directory is a BaseMount directory
"""
+ if not os.path.exists(self.path):
+ return False
if os.path.isdir(self.path):
for required in REQUIRED_ENTRIES:
required_path = os.path.join(self.path, required)
@@ -43,7 +46,7 @@ def __validate_basemount__(self):
def __resolve_details__(self):
"""
- pull the useful details out of the . files generated by metaBSFS
+ pull the useful details out of the . files generated by BaseMount
"""
if os.path.isdir(self.path):
type_file = os.path.join(self.path, ".type")
@@ -58,13 +61,33 @@ def __resolve_details__(self):
if self.type == "file":
metadata_path = self.path.replace("Files", "Files.metadata")
id_file = os.path.join(metadata_path, ".id")
+ config_file = os.path.join(os.path.dirname(self.path), ".basemount", "Config.cfg")
else:
id_file = os.path.join(self.path, ".id")
+ config_file = os.path.join(self.path, ".basemount", "Config.cfg")
+ if os.path.isfile(config_file):
+ # get the access token if we can
+ self.access_token = self._get_access_token_from_config(config_file)
self.id = open(id_file).read().strip()
def __str__(self):
return "%s : (%s) : (%s)" % (self.path, self.id, self.type)
+ def _get_access_token_from_config(self, config_path):
+ try:
+ from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError
+ except ImportError:
+ from configparser import SafeConfigParser, NoSectionError, NoOptionError
+ config = SafeConfigParser()
+ config.read(config_path)
+ try:
+ return config.get("DEFAULT", "accessToken")
+ except NoOptionError as NoSectionError:
+ raise BaseMountInterfaceException("malformed BaseMount config: %s" % config_path)
+
+
+
+
def get_meta_data(self):
try:
with open(os.path.join(self.path, ".json")) as fh:
@@ -85,4 +108,4 @@ def get_meta_data(self):
import sys
path = sys.argv[1]
mbi = BaseMountInterface(path)
- print mbi
\ No newline at end of file
+ print(mbi)
diff --git a/src/BaseSpacePy/api/BaseSpaceAPI.py b/src/BaseSpacePy/api/BaseSpaceAPI.py
index ba8ccbe..c6bd62a 100755
--- a/src/BaseSpacePy/api/BaseSpaceAPI.py
+++ b/src/BaseSpacePy/api/BaseSpaceAPI.py
@@ -1,18 +1,14 @@
from pprint import pprint
-import urllib2
import shutil
-import urllib
-import httplib
-import cStringIO
import json
import os
import re
from tempfile import mkdtemp
import socket
-import ConfigParser
-import urlparse
import logging
+import getpass
+import requests
from BaseSpacePy.api.APIClient import APIClient
from BaseSpacePy.api.BaseAPI import BaseAPI
@@ -21,6 +17,13 @@
from BaseSpacePy.model.MultipartFileTransfer import MultipartDownload as mpd
from BaseSpacePy.model.QueryParameters import QueryParameters as qp
from BaseSpacePy.model import *
+import six
+from six import moves
+from six.moves import http_client
+from six.moves import urllib
+
+from six.moves import configparser
+
# Uris for obtaining a access token, user verification code, and app trigger information
tokenURL = '/oauthv2/token'
@@ -37,22 +40,22 @@ class BaseSpaceAPI(BaseAPI):
'''
The main API class used for all communication with the REST server
'''
- def __init__(self, clientKey=None, clientSecret=None, apiServer=None, version=None, appSessionId='', AccessToken='', userAgent=None, timeout=10, verbose=0, profile='DEFAULT'):
+ def __init__(self, clientKey=None, clientSecret=None, apiServer=None, version='v1pre3', appSessionId='', AccessToken='', userAgent=None, timeout=10, verbose=0, profile='default'):
'''
- The following arguments are required in either the constructor or a config file (~/.basespacepy.cfg):
-
+ The following arguments are required in either the constructor or a config file (~/.basespacepy.cfg):
+
:param clientKey: the client key of the user's app; required in constructor or config file
:param clientSecret: the client secret of the user's app; required in constructor or config file
:param apiServer: the URL of the BaseSpace api server; required in constructor or config file
:param version: the version of the BaseSpace API; required in constructor or config file
:param appSessionId: optional, though may be needed for AppSession-related methods
:param AccessToken: optional, though will be needed for most methods (except to obtain a new access token)
- :param timeout: optional, timeout period in seconds for api calls, default 10
+ :param timeout: optional, timeout period in seconds for api calls, default 10
:param profile: optional, name of profile in config file, default 'DEFAULT'
'''
-
+
cred = self._setCredentials(clientKey, clientSecret, apiServer, version, appSessionId, AccessToken, profile)
-
+
self.appSessionId = cred['appSessionId']
self.key = cred['clientKey']
self.secret = cred['clientSecret']
@@ -62,8 +65,8 @@ def __init__(self, clientKey=None, clientSecret=None, apiServer=None, version=No
self.profile = cred['profile']
# TODO this replacement won't work for all environments
self.weburl = cred['apiServer'].replace('api.','')
-
- apiServerAndVersion = urlparse.urljoin(cred['apiServer'], cred['apiVersion'])
+
+ apiServerAndVersion = urllib.parse.urljoin(cred['apiServer'], cred['apiVersion'])
super(BaseSpaceAPI, self).__init__(cred['accessToken'], apiServerAndVersion, userAgent, timeout, verbose)
def _setCredentials(self, clientKey, clientSecret, apiServer, apiVersion, appSessionId, accessToken, profile):
@@ -78,219 +81,192 @@ def _setCredentials(self, clientKey, clientSecret, apiServer, apiVersion, appSes
:param apiServer: the URL of the BaseSpace api server
:param version: the version of the BaseSpace API
:param appSessionId: the AppSession Id
- :param AccessToken: an access token
- :param profile: name of profile in config file
+ :param AccessToken: an access token
+ :param profile: name of the config file
:returns: dictionary with credentials from constructor, config file, or default (for optional args), in this priority order.
'''
lcl_cred = self._getLocalCredentials(profile)
+ my_path = os.path.dirname(os.path.abspath(__file__))
+ authenticate_cmd = "bs authenticate"
+ if profile != "default":
+ authenticate_cmd = "%s --config %s" % (authenticate_cmd, profile)
cred = {}
+ # if access tokens have not been provided through the constructor,
+ # set a profile name
+ if not accessToken:
+ if 'name' in lcl_cred:
+ cred['profile'] = lcl_cred['name']
+ else:
+ cred['profile'] = profile
# required credentials
- if clientKey is not None:
- cred['clientKey'] = clientKey
- else:
- try:
- cred['clientKey'] = lcl_cred['clientKey']
- except KeyError:
- raise CredentialsException('Client Key not available - please provide in BaseSpaceAPI constructor or config file')
+ REQUIRED = ["accessToken", "apiServer", "apiVersion"]
+ for conf_item in REQUIRED:
+ local_value = locals()[conf_item]
+ if local_value:
+ cred[conf_item] = local_value
else:
- # set profile name
- if 'name' in lcl_cred:
- cred['profile'] = lcl_cred['name']
- else:
- cred['profile'] = profile
- if clientSecret is not None:
- cred['clientSecret'] = clientSecret
- else:
- try:
- cred['clientSecret'] = lcl_cred['clientSecret']
- except KeyError:
- raise CredentialsException('Client Secret not available - please provide in BaseSpaceAPI constructor or config file')
- if apiServer is not None:
- cred['apiServer'] = apiServer
- else:
- try:
- cred['apiServer'] = lcl_cred['apiServer']
- except KeyError:
- raise CredentialsException('API Server URL not available - please provide in BaseSpaceAPI constructor or config file')
- if apiVersion is not None:
- cred['apiVersion'] = apiVersion
- else:
- try:
- cred['apiVersion'] = lcl_cred['apiVersion']
- except KeyError:
- raise CredentialsException('API version available - please provide in BaseSpaceAPI constructor or config file')
- # Optional credentials
- if appSessionId:
- cred['appSessionId'] = appSessionId
- elif 'apiVersion' in lcl_cred:
- try:
- cred['appSessionId'] = lcl_cred['appSessionId']
- except KeyError:
- cred['appSessionId'] = appSessionId
- else:
- cred['appSessionId'] = appSessionId
-
- if accessToken:
- cred['accessToken'] = accessToken
- elif 'accessToken' in lcl_cred:
- try:
- cred['accessToken'] = lcl_cred['accessToken']
- except KeyError:
- cred['accessToken'] = accessToken
- else:
- cred['accessToken'] = accessToken
-
- return cred
+ try:
+ cred[conf_item] = lcl_cred[conf_item]
+ except KeyError:
+ raise CredentialsException("%s not found or config %s missing. Try running \"%s\"" % (conf_item, profile, authenticate_cmd))
+ # Optional credentials
+ OPTIONAL = ["clientKey", "clientSecret", "appSessionId"]
+ for conf_item in OPTIONAL:
+ local_value = locals()[conf_item]
+ if local_value:
+ cred[conf_item] = local_value
+ else:
+ try:
+ cred[conf_item] = lcl_cred[conf_item]
+ except KeyError:
+ cred[conf_item] = local_value
+ return cred
def _getLocalCredentials(self, profile):
'''
- Returns credentials from local config file (~/.basespacepy.cfg)
+ Returns credentials from local config file (~/.basespace/.cfg)
If some or all credentials are missing, they aren't included the in the returned dict
-
- :param profile: Profile name from local config file
- :returns: A dictionary with credentials from local config file
+
+ :param profile: Profile name to use to find local config file
+ :returns: A dictionary with credentials from local config file
'''
- config_file = os.path.expanduser('~/.basespacepy.cfg')
- cred = {}
- config = ConfigParser.SafeConfigParser()
+ config_file = os.path.join(os.path.expanduser('~/.basespace'), "%s.cfg" % profile)
+ if not os.path.exists(config_file):
+ raise CredentialsException("Could not find config file: %s" % config_file)
+ section_name = "DEFAULT"
+ cred = {}
+ config = configparser.SafeConfigParser()
if config.read(config_file):
- if not config.has_section(profile) and profile.lower() != 'default':
- raise CredentialsException("Profile name '%s' not present in config file %s" % (profile, config_file))
+ cred['name'] = profile
try:
- cred['name'] = config.get(profile, "name")
- except ConfigParser.NoOptionError:
+ cred['clientKey'] = config.get(section_name, "clientKey")
+ except configparser.NoOptionError:
pass
try:
- cred['clientKey'] = config.get(profile, "clientKey")
- except ConfigParser.NoOptionError:
+ cred['clientSecret'] = config.get(section_name, "clientSecret")
+ except configparser.NoOptionError:
pass
try:
- cred['clientSecret'] = config.get(profile, "clientSecret")
- except ConfigParser.NoOptionError:
+ cred['apiServer'] = config.get(section_name, "apiServer")
+ except configparser.NoOptionError:
pass
try:
- cred['apiServer'] = config.get(profile, "apiServer")
- except ConfigParser.NoOptionError:
+ cred['apiVersion'] = config.get(section_name, "apiVersion")
+ except configparser.NoOptionError:
pass
try:
- cred['apiVersion'] = config.get(profile, "apiVersion")
- except ConfigParser.NoOptionError:
- pass
- try:
- cred['appSessionId'] = config.get(profile, "appSessionId")
- except ConfigParser.NoOptionError:
+ cred['appSessionId'] = config.get(section_name, "appSessionId")
+ except configparser.NoOptionError:
pass
try:
- cred['accessToken'] = config.get(profile, "accessToken")
- except ConfigParser.NoOptionError:
- pass
+ cred['accessToken'] = config.get(section_name, "accessToken")
+ except configparser.NoOptionError:
+ pass
return cred
def getAppSessionById(self, Id):
'''
Get metadata about an AppSession.
Note that the client key and secret must match those of the AppSession's Application.
-
+
:param Id: The Id of the AppSession
:returns: An AppSession instance
- '''
+ '''
return self.getAppSession(Id=Id)
def getAppSessionOld(self, Id=None):
'''
- Get metadata about an AppSession.
- Note that the client key and secret must match those of the AppSession's Application.
-
- :param Id: an AppSession Id; if not provided, the AppSession Id of the BaseSpaceAPI instance will be used
- :returns: An AppSession instance
+ Get metadata about an AppSession.
+ Note that the client key and secret must match those of the AppSession's Application.
+
+ :param Id: an AppSession Id; if not provided, the AppSession Id of the BaseSpaceAPI instance will be used
+ :returns: An AppSession instance
'''
- # pycurl is hard to get working, so best to cauterise it into only the functions where it is needed
if Id is None:
Id = self.appSessionId
if not Id:
raise AppSessionException("An AppSession Id is required")
- resourcePath = self.apiClient.apiServerAndVersion + '/appsessions/{AppSessionId}'
- resourcePath = resourcePath.replace('{AppSessionId}', Id)
- response = cStringIO.StringIO()
- # import pycurl
- # c = pycurl.Curl()
- # c.setopt(pycurl.URL, resourcePath)
- # c.setopt(pycurl.USERPWD, self.key + ":" + self.secret)
- # c.setopt(c.WRITEFUNCTION, response.write)
- # c.perform()
- # c.close()
- # resp_dict = json.loads(response.getvalue())
+ resourcePath = self.apiClient.apiServerAndVersion + '/appsessions/{AppSessionId}'
+ resourcePath = resourcePath.replace('{AppSessionId}', Id)
+ response = moves.cStringIO()
import requests
response = requests.get(resourcePath, auth=(self.key, self.secret))
resp_dict = json.loads(response.text)
- return self.__deserializeAppSessionResponse__(resp_dict)
+ return self.__deserializeAppSessionResponse__(resp_dict)
def getAppSession(self, Id=None, queryPars=None):
if Id is None:
Id = self.appSessionId
if not Id:
raise AppSessionException("An AppSession Id is required")
- resourcePath = '/appsessions/{AppSessionId}'
- resourcePath = resourcePath.replace('{AppSessionId}', Id)
+ resourcePath = '/appsessions/{AppSessionId}'
+ resourcePath = resourcePath.replace('{AppSessionId}', Id)
method = 'GET'
headerParams = {}
queryParams = {}
return self.__singleRequest__(AppSessionResponse.AppSessionResponse, resourcePath, method, queryParams, headerParams)
+ def getAllAppSessions(self, queryPars=None):
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/users/current/appsessions'
+ method = 'GET'
+ headerParams = {}
+ return self.__listRequest__(AppSession.AppSession, resourcePath, method, queryParams, headerParams)
+
def __deserializeAppSessionResponse__(self, response):
'''
- Converts a AppSession response from the API server to an AppSession object.
-
+ Converts a AppSession response from the API server to an AppSession object.
+
:param response: a dictionary (decoded from json) from getting an AppSession from the api server
- :returns: An AppSession instance
- '''
- if response['ResponseStatus'].has_key('ErrorCode'):
- raise AppSessionException('BaseSpace error: ' + str(response['ResponseStatus']['ErrorCode']) + ": " + response['ResponseStatus']['Message'])
+ :returns: An AppSession instance
+ '''
+ if 'ErrorCode' in response['ResponseStatus']:
+ raise AppSessionException('BaseSpace error: ' + str(response['ResponseStatus']['ErrorCode']) + ": " + response['ResponseStatus']['Message'])
tempApi = APIClient(AccessToken='', apiServerAndVersion=self.apiClient.apiServerAndVersion, userAgent=self.apiClient.userAgent)
- res = tempApi.deserialize(response, AppSessionResponse.AppSessionResponse)
+ res = tempApi.deserialize(response, AppSessionResponse.AppSessionResponse)
return res.Response.__deserializeReferences__(self)
def getAppSessionPropertiesById(self, Id, queryPars=None):
'''
Returns the Properties of an AppSession
-
+
:param Id: An AppSession Id
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
- :returns: A PropertyList instance
- '''
- queryParams = self._validateQueryParameters(queryPars)
+ :returns: A PropertyList instance
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/appsessions/{Id}/properties'
resourcePath = resourcePath.replace('{Id}',Id)
- method = 'GET'
- headerParams = {}
+ method = 'GET'
+ headerParams = {}
return self.__singleRequest__(PropertiesResponse.PropertiesResponse, resourcePath, method, queryParams, headerParams)
def getAppSessionPropertyByName(self, Id, name, queryPars=None):
'''
Returns the multi-value Property of the provided AppSession that has the provided Property name.
Note - this method (and REST API) is supported for ONLY multi-value Properties.
-
+
:param Id: The AppSessionId
:param name: Name of the multi-value property to retrieve
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
- :returns: A multi-value propertylist instance such as MultiValuePropertyAppResultsList (depending on the Property Type)
+ :returns: A multi-value propertylist instance such as MultiValuePropertyAppResultsList (depending on the Property Type)
'''
- queryParams = self._validateQueryParameters(queryPars)
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/appsessions/{Id}/properties/{Name}/items'
resourcePath = resourcePath.replace('{Id}', Id)
- resourcePath = resourcePath.replace('{Name}', name)
- method = 'GET'
+ resourcePath = resourcePath.replace('{Name}', name)
+ method = 'GET'
headerParams = {}
return self.__singleRequest__(MultiValuePropertyResponse.MultiValuePropertyResponse, resourcePath, method, queryParams, headerParams)
-
+
def getAppSessionInputsById(self, Id, queryPars=None):
'''
Returns the input properties of an AppSession
-
+
:param Id: An AppSessionId
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
- :returns: a dictionary of input properties, keyed by input Name
- '''
+ :returns: a dictionary of input properties, keyed by input Name
+ '''
props = self.getAppSessionPropertiesById(Id, queryPars)
inputs = {}
for prop in props.Items:
@@ -303,13 +279,13 @@ def setAppSessionState(self, Id, Status, Summary):
'''
Set the Status and StatusSummary of an AppSession in BaseSpace.
Note - once Status is set to Completed or Aborted, no further changes can made.
-
+
:param Id: The id of the AppSession
:param Status: The AppSession status string, must be one of: Running, Complete, NeedsAttention, TimedOut, Aborted
:param Summary: The status summary string
:returns: An updated AppSession instance
'''
- resourcePath = '/appsessions/{Id}'
+ resourcePath = '/appsessions/{Id}'
method = 'POST'
resourcePath = resourcePath.replace('{Id}', Id)
queryParams = {}
@@ -322,12 +298,31 @@ def setAppSessionState(self, Id, Status, Summary):
postData['statussummary'] = Summary
return self.__singleRequest__(AppSessionResponse.AppSessionResponse, resourcePath, method, queryParams, headerParams, postData=postData)
+ def stopAppSession(self, Id):
+ """
+ Unfortunately, the v1pre3 appsession stop endpoint does not support tokens,
+ so this method has to create a special API object to call a v2 endpoint :(
+
+ :param Id:
+ :return: An AppSessionResponse that contains the appsession we just stopped
+ """
+ resourcePath = '/appsessions/{Id}/stop'
+ method = 'POST'
+ resourcePath = resourcePath.replace('{Id}', Id)
+ queryParams = {}
+ headerParams = {}
+ postData = {}
+ apiServerAndVersion = urllib.parse.urljoin(self.apiServer, "v2")
+ v2api = BaseAPI(self.getAccessToken(), apiServerAndVersion)
+ return v2api.__singleRequest__(AppSessionResponse.AppSessionResponse, resourcePath, method, queryParams,
+ headerParams, postData=postData)
+
def __deserializeObject__(self, dct, type):
'''
Converts API response into object instances for Projects, Samples, and AppResults.
For other types, the input value is simply returned.
-
- (Currently called by Sample's getReferencedAppResults() and
+
+ (Currently called by Sample's getReferencedAppResults() and
AppSessionLaunchObject's __deserializeObject__() to serialize References)
:param dct: dictionary from an API response (converted from JSON) for a BaseSpace item (eg., a Project)
@@ -340,13 +335,13 @@ def __deserializeObject__(self, dct, type):
if type.lower()=='sample':
return tempApi.deserialize(dct, Sample.Sample)
if type.lower()=='appresult':
- return tempApi.deserialize(dct, AppResult.AppResult)
- return dct
-
+ return tempApi.deserialize(dct, AppResult.AppResult)
+ return dct
+
def getAccess(self, obj, accessType='write', web=False, redirectURL='', state=''):
'''
Requests access to the provided BaseSpace object.
-
+
:param obj: The data object we wish to get access to -- must be a Project, Sample, AppResult, or Run.
:param accessType: (Optional) the type of access (browse|read|write|create), default is write. Create is only supported for Projects.
:param web: (Optional) true if the App is web-based, default is false meaning a device based app
@@ -363,12 +358,12 @@ def getAccess(self, obj, accessType='write', web=False, redirectURL='', state=''
return self.getWebVerificationCode(scopeStr, redirectURL, state)
else:
return self.getVerificationCode(scopeStr)
-
+
def getVerificationCode(self, scope):
'''
- For non-web applications (eg. devices), returns the device code
- and verification url for the user to approve access to a specific data scope.
-
+ For non-web applications (eg. devices), returns the device code
+ and verification url for the user to approve access to a specific data scope.
+
:param scope: The scope that access is requested for (e.g. 'browse project 123')
:returns: dictionary of server response
'''
@@ -378,21 +373,21 @@ def getVerificationCode(self, scope):
def getWebVerificationCode(self, scope, redirectURL, state=''):
'''
Generates the URL the user should be redirected to for web-based authentication
-
+
:param scope: The scope that access is requested for (e.g. 'browse project 123')
:param redirectURL: The redirect URL
:param state: (Optional) A state parameter that will passed through to the redirect response
- :returns: a url
- '''
+ :returns: a url
+ '''
data = {'client_id': self.key, 'redirect_uri': redirectURL, 'scope': scope, 'response_type': 'code', "state": state}
- return self.weburl + webAuthorize + '?' + urllib.urlencode(data)
+ return self.weburl + webAuthorize + '?' + urllib.parse.urlencode(data)
def obtainAccessToken(self, code, grantType='device', redirect_uri=None):
'''
- Returns a user specific access token, for either device (non-web) or web apps.
-
+ Returns a user specific access token, for either device (non-web) or web apps.
+
:param code: The device code returned by the getVerificationCode method
- :param grantType: Grant-type may be either 'device' for non-web apps (default), or 'authorization_code' for web apps
+ :param grantType: Grant-type may be either 'device' for non-web apps (default), or 'authorization_code' for web apps
:param redirect_uri: The uri to redirect to; required for web apps only.
:raises OAuthException: when redirect_uri isn't provided by web apps
:returns: an access token
@@ -403,32 +398,61 @@ def obtainAccessToken(self, code, grantType='device', redirect_uri=None):
resp_dict = self.__makeCurlRequest__(data, self.apiClient.apiServerAndVersion + tokenURL)
return str(resp_dict['access_token'])
+ def getAccessTokenDetails(self):
+ '''
+ Because this does not use the standard API prefix, this has to be done as a special case
+ :return:
+ '''
+ endpoint = self.apiClient.apiServerAndVersion + tokenURL + "/current"
+
+ args = {
+ "access_token": self.apiClient.apiKey
+ }
+ try:
+ response_raw = requests.get(endpoint, args)
+ response = response_raw.json()
+ except Exception as e:
+ raise ServerResponseException('Could not query access token endpoint: %s' % str(e))
+ if not response:
+ raise ServerResponseException('No response returned')
+ if 'ResponseStatus' in response:
+ if 'ErrorCode' in response['ResponseStatus']:
+ raise ServerResponseException(str(response['ResponseStatus']['ErrorCode'] + ": " + response['ResponseStatus']['Message']))
+ elif 'Message' in response['ResponseStatus']:
+ raise ServerResponseException(str(response['ResponseStatus']['Message']))
+ elif 'ErrorCode' in response:
+ raise ServerResponseException(response["MessageFormatted"])
+
+ responseObject = self.apiClient.deserialize(response["Response"], Token.Token)
+ return responseObject
+
+
def updatePrivileges(self, code, grantType='device', redirect_uri=None):
'''
Retrieves a user-specific access token, and sets the token on the current object.
:param code: The device code returned by the getVerificationCode method
- :param grantType: Grant-type may be either 'device' for non-web apps (default), or 'authorization_code' for web apps
+ :param grantType: Grant-type may be either 'device' for non-web apps (default), or 'authorization_code' for web apps
:param redirect_uri: The uri to redirect to; required for web apps only.
- :returns: None
+ :returns: None
'''
token = self.obtainAccessToken(code, grantType=grantType, redirect_uri=redirect_uri)
self.setAccessToken(token)
-
+
def createProject(self, Name):
'''
- Creates a project with the specified name and returns a project object.
+ Creates a project with the specified name and returns a project object.
If a project with this name already exists, the existing project is returned.
-
+
:param Name: Name of the project
- :returns: a Project instance of the newly created project
- '''
- resourcePath = '/projects/'
+ :returns: a Project instance of the newly created project
+ '''
+ resourcePath = '/projects/'
method = 'POST'
queryParams = {}
headerParams = {}
postData = {}
- postData['Name'] = Name
+ postData['Name'] = Name
return self.__singleRequest__(ProjectResponse.ProjectResponse,
resourcePath, method, queryParams, headerParams, postData=postData)
@@ -438,26 +462,26 @@ def launchApp(self, appId, configJson):
queryParams = {}
headerParams = { 'Content-Type' : "application/json" }
postData = configJson
- return self.__singleRequest__(AppLaunchResponse.AppLaunchResponse,
+ return self.__singleRequest__(AppLaunchResponse.AppLaunchResponse,
resourcePath, method, queryParams, headerParams, postData=postData)
def getUserById(self, Id):
'''
Returns the User object corresponding to User Id
-
+
:param Id: The Id of the user
:returns: a User instance
- '''
- resourcePath = '/users/{Id}'
+ '''
+ resourcePath = '/users/{Id}'
method = 'GET'
resourcePath = resourcePath.replace('{Id}', Id)
queryParams = {}
headerParams = {}
return self.__singleRequest__(UserResponse.UserResponse, resourcePath, method, queryParams, headerParams)
-
+
def getAppResultFromAppSessionId(self, Id, appResultName=""):
'''
- Returns an AppResult object from an AppSession Id.
+ Returns an AppResult object from an AppSession Id.
if appResultName is supplied, look for an appresult with this name
otherwise, expect there to be exactly one appresult
@@ -480,47 +504,47 @@ def getAppResultFromAppSessionId(self, Id, appResultName=""):
def getAppResultById(self, Id, queryPars=None):
'''
Returns an AppResult object corresponding to Id
-
+
:param Id: The Id of the AppResult
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: an AppResult instance
- '''
+ '''
queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/appresults/{Id}'
resourcePath = resourcePath.replace('{format}', 'json')
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(AppResultResponse.AppResultResponse,resourcePath, method, queryParams, headerParams)
def getAppResultPropertiesById(self, Id, queryPars=None):
'''
Returns the Properties of an AppResult object corresponding to AppResult Id
-
+
:param Id: The Id of the AppResult
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a PropertyList instance
- '''
- queryParams = self._validateQueryParameters(queryPars)
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/appresults/{Id}/properties'
resourcePath = resourcePath.replace('{format}', 'json')
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(PropertiesResponse.PropertiesResponse, resourcePath, method, queryParams, headerParams)
def getAppResultFilesById(self, Id, queryPars=None):
'''
Returns a list of File object for an AppResult
-
+
:param Id: The id of the AppResult
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
- :returns: a list of File instances
+ :returns: a list of File instances
'''
- queryParams = self._validateQueryParameters(queryPars)
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/appresults/{Id}/files'
resourcePath = resourcePath.replace('{format}', 'json')
- method = 'GET'
+ method = 'GET'
headerParams = {}
resourcePath = resourcePath.replace('{Id}',Id)
return self.__listRequest__(File.File,resourcePath, method, queryParams, headerParams)
@@ -528,12 +552,12 @@ def getAppResultFilesById(self, Id, queryPars=None):
def getAppResultFiles(self, Id, queryPars=None):
'''
* Deprecated in favor of getAppResultFileById() *
-
+
Returns a list of File object for an AppResult
-
+
:param Id: The id of the AppResult
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
- :returns: a list of File instances
+ :returns: a list of File instances
'''
return self.getAppResultFilesById(Id, queryPars)
@@ -563,138 +587,150 @@ def downloadAppResultFilesByExtension(self, Id, extension, localDir, appResultNa
def getProjectById(self, Id, queryPars=None):
'''
Request a project object by Id
-
+
:param Id: The Id of the project
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a Project instance
'''
- queryParams = self._validateQueryParameters(queryPars)
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/projects/{Id}'
resourcePath = resourcePath.replace('{format}', 'json')
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(ProjectResponse.ProjectResponse, resourcePath, method, queryParams, headerParams)
def getProjectPropertiesById(self, Id, queryPars=None):
'''
Request the Properties of a project object by Id
-
+
:param Id: The Id of the project
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a ProjectList instance
'''
- queryParams = self._validateQueryParameters(queryPars)
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/projects/{Id}/properties'
resourcePath = resourcePath.replace('{format}', 'json')
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(PropertiesResponse.PropertiesResponse,resourcePath, method, queryParams, headerParams)
-
+
def getProjectByUser(self, queryPars=None):
'''
Returns a list available projects for the current User.
-
+
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of Project instances
'''
- queryParams = self._validateQueryParameters(queryPars)
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/users/current/projects'
resourcePath = resourcePath.replace('{format}', 'json')
- method = 'GET'
+ method = 'GET'
headerParams = {}
return self.__listRequest__(Project.Project,resourcePath, method, queryParams, headerParams)
-
+
+ def getUserProjectByName(self, projectName):
+ '''
+
+ :return: project matching the provided name
+ '''
+ projects = self.getProjectByUser(qp({"Name":projectName}))
+ if len(projects) == 0:
+ raise ServerResponseException("No such project: %s" % projectName)
+ if len(projects) > 1:
+ raise ServerResponseException("More than one matching projects: %s" % projectName)
+ return projects[0]
+
def getAccessibleRunsByUser(self, queryPars=None):
'''
Returns a list of accessible runs for the current User
-
+
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of Run instances
- '''
- queryParams = self._validateQueryParameters(queryPars)
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/users/current/runs'
resourcePath = resourcePath.replace('{format}', 'json')
- method = 'GET'
+ method = 'GET'
headerParams = {}
return self.__listRequest__(Run.Run, resourcePath, method, queryParams, headerParams)
-
+
def getRunById(self, Id, queryPars=None):
- '''
+ '''
Request a run object by Id
-
+
:param Id: The Id of the run
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a Run instance
- '''
- queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/runs/{Id}'
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/runs/{Id}'
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(RunResponse.RunResponse,resourcePath, method, queryParams, headerParams)
-
+
def getRunPropertiesById(self, Id, queryPars=None):
- '''
+ '''
Request the Properties of a run object by Id
-
+
:param Id: The Id of the run
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a PropertyList instance
- '''
- queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/runs/{Id}/properties'
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/runs/{Id}/properties'
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(PropertiesResponse.PropertiesResponse,resourcePath, method, queryParams, headerParams)
def getRunFilesById(self, Id, queryPars=None):
- '''
+ '''
Request the files associated with a Run, using the Run's Id
-
+
:param Id: The Id of the run
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of Run instances
- '''
- queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/runs/{Id}/files'
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/runs/{Id}/files'
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
- headerParams = {}
+ resourcePath = resourcePath.replace('{Id}', Id)
+ headerParams = {}
return self.__listRequest__(File.File,resourcePath, method, queryParams, headerParams)
def getRunSamplesById(self, Id, queryPars=None):
- '''
+ '''
Request the Samples associated with a Run, using the Run's Id
-
+
:param Id: The Id of the run
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of Sample instances
- '''
- queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/runs/{Id}/samples'
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/runs/{Id}/samples'
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
- headerParams = {}
+ resourcePath = resourcePath.replace('{Id}', Id)
+ headerParams = {}
return self.__listRequest__(Sample.Sample,resourcePath, method, queryParams, headerParams)
-
+
def getAppResultsByProject(self, Id, queryPars=None, statuses=None):
'''
Returns a list of AppResult object associated with the project with Id
-
+
:param Id: The project id
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:param statuses: An (optional) list of AppResult statuses to filter by, eg., 'complete'
:returns: a list of AppResult instances
'''
- queryParams = self._validateQueryParameters(queryPars)
+ queryParams = self._validateQueryParameters(queryPars)
if statuses is None:
- statuses = []
- resourcePath = '/projects/{Id}/appresults'
- method = 'GET'
- if len(statuses):
+ statuses = []
+ resourcePath = '/projects/{Id}/appresults'
+ method = 'GET'
+ if len(statuses):
queryParams['Statuses'] = ",".join(statuses)
headerParams = {}
resourcePath = resourcePath.replace('{Id}',Id)
@@ -703,15 +739,15 @@ def getAppResultsByProject(self, Id, queryPars=None, statuses=None):
def getSamplesByProject(self, Id, queryPars=None):
'''
Returns a list of samples associated with a project with Id
-
+
:param Id: The id of the project
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of Sample instances
'''
- queryParams = self._validateQueryParameters(queryPars)
+ queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/projects/{Id}/samples'
resourcePath = resourcePath.replace('{format}', 'json')
- method = 'GET'
+ method = 'GET'
headerParams = {}
resourcePath = resourcePath.replace('{Id}',Id)
return self.__listRequest__(Sample.Sample,resourcePath, method, queryParams, headerParams)
@@ -719,22 +755,22 @@ def getSamplesByProject(self, Id, queryPars=None):
def getSampleById(self, Id, queryPars=None):
'''
Returns a Sample object
-
+
:param Id: The id of the sample
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a Sample instance
'''
- queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/samples/{Id}'
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/samples/{Id}'
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(SampleResponse.SampleResponse, resourcePath, method, queryParams, headerParams)
-
+
def getSamplePropertiesById(self, Id, queryPars=None):
'''
Returns the Properties of a Sample object
-
+
:param Id: The id of the sample
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a PropertyList instance
@@ -750,14 +786,14 @@ def getSamplePropertiesById(self, Id, queryPars=None):
def getSampleFilesById(self, Id, queryPars=None):
'''
Returns a list of File objects associated with a Sample
-
+
:param Id: A Sample id
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of File instances
'''
queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/samples/{Id}/files'
- method = 'GET'
+ resourcePath = '/samples/{Id}/files'
+ method = 'GET'
headerParams = {}
resourcePath = resourcePath.replace('{Id}',Id)
return self.__listRequest__(File.File,
@@ -766,43 +802,43 @@ def getSampleFilesById(self, Id, queryPars=None):
def getFilesBySample(self, Id, queryPars=None):
'''
* Deprecated in favor of getSampleFilesById() *
-
+
Returns a list of File objects associated with a Sample
-
+
:param Id: A Sample id
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of File instances
'''
- return self.getSampleFilesById(Id, queryPars)
-
+ return self.getSampleFilesById(Id, queryPars)
+
def getFileById(self, Id, queryPars=None):
'''
Returns a file object by Id
-
+
:param Id: The id of the file
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a File instance
'''
- queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/files/{Id}'
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/files/{Id}'
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(FileResponse.FileResponse,
resourcePath, method, queryParams, headerParams)
-
+
def getFilePropertiesById(self, Id, queryPars=None):
'''
Returns the Properties of a file object by Id
-
+
:param Id: The id of the file
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a PropertyList instance
- '''
- queryParams = self._validateQueryParameters(queryPars)
- resourcePath = '/files/{Id}/properties'
+ '''
+ queryParams = self._validateQueryParameters(queryPars)
+ resourcePath = '/files/{Id}/properties'
method = 'GET'
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
return self.__singleRequest__(PropertiesResponse.PropertiesResponse,
resourcePath, method, queryParams, headerParams)
@@ -810,7 +846,7 @@ def getFilePropertiesById(self, Id, queryPars=None):
def getGenomeById(self, Id, ):
'''
Returns an instance of Genome with the specified Id
-
+
:param Id: The genome id
:returns: a GenomeV1 instance
'''
@@ -827,26 +863,26 @@ def getGenomeById(self, Id, ):
def getAvailableGenomes(self, queryPars=None):
'''
Returns a list of all available genomes
-
+
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:returns: a list of GenomeV1 instances
- '''
+ '''
queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/genomes'
method = 'GET'
headerParams = {}
return self.__listRequest__(GenomeV1.GenomeV1,
- resourcePath, method, queryParams, headerParams)
+ resourcePath, method, queryParams, headerParams, sort=False)
def getIntervalCoverage(self, Id, Chrom, StartPos, EndPos):
'''
Returns metadata about an alignment, including max coverage and cov granularity.
- Note that HrefCoverage must be available for the provided BAM file.
-
+ Note that HrefCoverage must be available for the provided BAM file.
+
:param Id: the Id of a BAM file
:param Chrom: chromosome name
:param StartPos: get coverage starting at this position
- :param EndPos: get coverage up to and including this position; the returned EndPos may be larger than requested due to rounding up to nearest window end coordinate
+ :param EndPos: get coverage up to and including this position; the returned EndPos may be larger than requested due to rounding up to nearest window end coordinate
:returns: a Coverage instance
'''
resourcePath = '/coverage/{Id}/{Chrom}'
@@ -864,7 +900,7 @@ def getCoverageMetaInfo(self, Id, Chrom):
'''
Returns metadata about coverage of a chromosome.
Note that HrefCoverage must be available for the provided BAM file
-
+
:param Id: the Id of a Bam file
:param Chrom: chromosome name
:returns: a CoverageMetaData instance
@@ -874,15 +910,15 @@ def getCoverageMetaInfo(self, Id, Chrom):
queryParams = {}
headerParams = {}
resourcePath = resourcePath.replace('{Chrom}', Chrom)
- resourcePath = resourcePath.replace('{Id}', Id)
+ resourcePath = resourcePath.replace('{Id}', Id)
return self.__singleRequest__(CoverageMetaResponse.CoverageMetaResponse,
resourcePath, method, queryParams, headerParams)
def filterVariantSet(self,Id, Chrom, StartPos, EndPos, Format='json', queryPars=None):
'''
List the variants in a set of variants. Note the maximum returned records is 1000.
-
- :param Id: The id of the variant file
+
+ :param Id: The id of the variant file
:param Chrom: Chromosome name
:param StartPos: The start position of the sequence of interest
:param EndPos: The start position of the sequence of interest
@@ -892,7 +928,7 @@ def filterVariantSet(self,Id, Chrom, StartPos, EndPos, Format='json', queryPars=
'''
queryParams = self._validateQueryParameters(queryPars)
resourcePath = '/variantset/{Id}/variants/{Chrom}'
- method = 'GET'
+ method = 'GET'
headerParams = {}
queryParams['StartPos'] = StartPos
queryParams['EndPos'] = EndPos
@@ -902,17 +938,17 @@ def filterVariantSet(self,Id, Chrom, StartPos, EndPos, Format='json', queryPars=
if Format == 'vcf':
raise NotImplementedError("Returning native VCF format isn't yet supported by BaseSpacePy")
else:
- return self.__listRequest__(Variant.Variant, resourcePath, method, queryParams, headerParams)
+ return self.__listRequest__(Variant.Variant, resourcePath, method, queryParams, headerParams, sort=False)
def getVariantMetadata(self, Id, Format='json'):
- '''
+ '''
Returns the header information of a VCF file.
-
+
:param Id: The Id of the VCF file
:param Format: (optional) The return-value format, set to 'json' (default) to return return an object (not actually json format), or 'vcf' (not implemented yet) to return a string in VCF format.
:returns: A VariantHeader instance
'''
- resourcePath = '/variantset/{Id}'
+ resourcePath = '/variantset/{Id}'
method = 'GET'
queryParams = {}
headerParams = {}
@@ -923,16 +959,16 @@ def getVariantMetadata(self, Id, Format='json'):
else:
return self.__singleRequest__(VariantsHeaderResponse.VariantsHeaderResponse,
resourcePath, method, queryParams, headerParams)
-
+
def createAppResult(self, Id, name, desc, samples=None, appSessionId=None):
'''
Create an AppResult object.
-
+
:param Id: The id of the project in which the AppResult is to be added
:param name: The name of the AppResult
:param desc: A description of the AppResult
- :param samples: (Optional) A list of one or more Samples Ids that the AppResult is related to
- :param appSessionId: (Optional) If no appSessionId is given, the id used to initialize the BaseSpaceAPI instance will be used. If appSessionId is set equal to an empty string, a new appsession will be created for the appresult object
+ :param samples: (Optional) A list of one or more Samples Ids that the AppResult is related to
+ :param appSessionId: (Optional) If no appSessionId is given, the id used to initialize the BaseSpaceAPI instance will be used. If appSessionId is set equal to an empty string, a new appsession will be created for the appresult object
:raises Exception: when attempting to create AppResult in an AppSession that has a status other than 'running'.
:returns: a newly created AppResult instance
'''
@@ -947,12 +983,12 @@ def createAppResult(self, Id, name, desc, samples=None, appSessionId=None):
queryParams = {}
headerParams = {}
postData = {}
-
+
if appSessionId:
queryParams['appsessionid'] = appSessionId
if appSessionId==None:
queryParams['appsessionid'] = self.appSessionId # default case, we use the current appsession
-
+
# add the sample references
if len(samples):
ref = []
@@ -961,28 +997,28 @@ def createAppResult(self, Id, name, desc, samples=None, appSessionId=None):
ref.append(d)
postData['References'] = ref
# case, an appSession is provided, we need to check if the app is running
- if queryParams.has_key('appsessionid'):
+ if 'appsessionid' in queryParams:
session = self.getAppSession(Id=queryParams['appsessionid'])
if not session.canWorkOn():
raise Exception('AppSession status must be "running," to create an AppResults. Current status is ' + session.Status)
-
+
postData['Name'] = name
postData['Description'] = desc
return self.__singleRequest__(AppResultResponse.AppResultResponse,
resourcePath, method, queryParams, headerParams, postData=postData)
-
+
def appResultFileUpload(self, Id, localPath, fileName, directory, contentType):
'''
Uploads a file associated with an AppResult to BaseSpace and returns the corresponding file object.
Small files are uploaded with a single-part upload method, while larger files (> 25 MB) are uploaded
with multipart upload.
-
+
:param Id: AppResult id.
:param localPath: The local path to the file to be uploaded, including file name.
:param fileName: The desired filename in the AppResult folder on the BaseSpace server.
:param directory: The directory the file should be placed in on the BaseSpace server.
:param contentType: The content-type of the file, eg. 'text/plain' for text files, 'application/octet-stream' for binary files
- :returns: a newly created File instance
+ :returns: a newly created File instance
'''
multipart_min_file_size = 25000000 # bytes
if os.path.getsize(localPath) > multipart_min_file_size:
@@ -993,11 +1029,11 @@ def appResultFileUpload(self, Id, localPath, fileName, directory, contentType):
def createSample(self, Id, name, experimentName, sampleNumber, sampleTitle, readLengths, countRaw, countPF, reference=None, appSessionId=None):
'''
Create a Sample object.
-
+
:param Id: The id of the project in which the Sample is to be added
:param name: The name of the Sample
- :param reference: (Optional) Reference genome that the sample relates to
- :param appSessionId: (Optional) If no appSessionId is given, the id used to initialize the BaseSpaceAPI instance will be used. If appSessionId is set equal to an empty string, a new appsession will be created for the sample object
+ :param reference: (Optional) Reference genome that the sample relates to
+ :param appSessionId: (Optional) If no appSessionId is given, the id used to initialize the BaseSpaceAPI instance will be used. If appSessionId is set equal to an empty string, a new appsession will be created for the sample object
:raises Exception: when attempting to create Sample in an AppSession that has a status other than 'running'.
:returns: a newly created Sample instance
'''
@@ -1012,18 +1048,18 @@ def createSample(self, Id, name, experimentName, sampleNumber, sampleTitle, read
queryParams = {}
headerParams = {}
postData = {}
-
+
if appSessionId:
queryParams['appsessionid'] = appSessionId
if appSessionId==None:
queryParams['appsessionid'] = self.appSessionId # default case, we use the current appsession
-
+
# case, an appSession is provided, we need to check if the app is running
- if queryParams.has_key('appsessionid'):
+ if 'appsessionid' in queryParams:
session = self.getAppSession(Id=queryParams['appsessionid'])
if not session.canWorkOn():
raise Exception('AppSession status must be "running," to create a Sample. Current status is ' + session.Status)
-
+
postData['Name'] = name
postData['ExperimentName'] = experimentName
postData['SampleNumber'] = sampleNumber
@@ -1064,14 +1100,14 @@ def __singlepartFileUpload__(self, resourceType, resourceId, localPath, fileName
'''
Uploads a file associated with an Endpoint to BaseSpace and returns the corresponding file object.
Intended for small files -- reads whole file into memory prior to upload.
-
+
:param resourceType: resource type for the property
:param resourceId: identifier for the resource
:param localPath: The local path to the file to be uploaded, including file name.
:param fileName: The desired filename in the Endpoint folder on the BaseSpace server.
:param directory: The directory the file should be placed in on the BaseSpace server.
:param contentType: The content-type of the file.
- :returns: a newly created File instance
+ :returns: a newly created File instance
'''
if resourceType not in PROPERTY_RESOURCE_TYPES:
raise IllegalParameterException(resourceType, PROPERTY_RESOURCE_TYPES)
@@ -1081,7 +1117,7 @@ def __singlepartFileUpload__(self, resourceType, resourceId, localPath, fileName
resourcePath = resourcePath.replace('{Resource}', resourceType)
queryParams = {}
queryParams['name'] = fileName
- queryParams['directory'] = directory
+ queryParams['directory'] = directory
headerParams = {}
headerParams['Content-Type'] = contentType
postData = open(localPath).read()
@@ -1090,14 +1126,14 @@ def __singlepartFileUpload__(self, resourceType, resourceId, localPath, fileName
def __initiateMultipartFileUpload__(self, resourceType, resourceId, fileName, directory, contentType):
'''
- Initiates multipart upload of a file to an AppResult in BaseSpace (does not actually upload file).
-
+ Initiates multipart upload of a file to an AppResult in BaseSpace (does not actually upload file).
+
:param resourceType: resource type for the property
:param resourceId: identifier for the resource
:param fileName: The desired filename in the AppResult folder on the BaseSpace server.
:param directory: The directory the file should be placed in on the BaseSpace server.
:param contentType: The content-type of the file, eg. 'text/plain' for text files, 'application/octet-stream' for binary files
- :returns: A newly created File instance
+ :returns: A newly created File instance
'''
if resourceType not in PROPERTY_RESOURCE_TYPES:
raise IllegalParameterException(resourceType, PROPERTY_RESOURCE_TYPES)
@@ -1107,10 +1143,10 @@ def __initiateMultipartFileUpload__(self, resourceType, resourceId, fileName, di
resourcePath = resourcePath.replace('{Resource}', resourceType)
queryParams = {}
queryParams['name'] = fileName
- queryParams['directory'] = directory
+ queryParams['directory'] = directory
headerParams = {}
headerParams['Content-Type'] = contentType
-
+
queryParams['multipart'] = 'true'
postData = None
# Set force post as this need to use POST though no data is being streamed
@@ -1120,12 +1156,12 @@ def __initiateMultipartFileUpload__(self, resourceType, resourceId, fileName, di
def __uploadMultipartUnit__(self, Id, partNumber, md5, data):
'''
Uploads file part for multipart upload
-
- :param Id: file id
+
+ :param Id: file id
:param partNumber: the file part to be uploaded
:param md5: md5 sum of datastream
:param data: the name of the file containing only data to be uploaded
- :returns: A dictionary of the server response, with a 'Response' key that contains a dict, which contains an 'ETag' key and value on success. On failure, this method returns None
+ :returns: A dictionary of the server response, with a 'Response' key that contains a dict, which contains an 'ETag' key and value on success. On failure, this method returns None
'''
method = 'PUT'
resourcePath = '/files/{Id}/parts/{partNumber}'
@@ -1133,12 +1169,12 @@ def __uploadMultipartUnit__(self, Id, partNumber, md5, data):
resourcePath = resourcePath.replace('{partNumber}', str(partNumber))
queryParams = {}
headerParams = {'Content-MD5':md5.strip()}
- return self.apiClient.callAPI(resourcePath, method, queryParams, data, headerParams=headerParams, forcePost=0)
+ return self.apiClient.callAPI(resourcePath, method, queryParams, data, headerParams=headerParams, forcePost=0)
def __finalizeMultipartFileUpload__(self, Id):
'''
- Marks a multipart upload file as complete
-
+ Marks a multipart upload file as complete
+
:param Id: the File Id
:returns: a File instance with UploadStatus attribute updated to 'complete'
'''
@@ -1147,22 +1183,21 @@ def __finalizeMultipartFileUpload__(self, Id):
resourcePath = resourcePath.replace('{Id}', Id)
headerParams = {}
queryParams = {'uploadstatus':'complete'}
- postData = None
+ postData = None
# Set force post as this need to use POST though no data is being streamed
return self.__singleRequest__(FileResponse.FileResponse,
resourcePath, method, queryParams, headerParams, postData=postData, forcePost=1)
- def multipartFileUpload(self, resourceType, resourceId, localPath, fileName, directory, contentType, tempDir=None, processCount=10, partSize=25):
+ def multipartFileUpload(self, resourceType, resourceId, localPath, fileName, directory, contentType, processCount=10, partSize=25):
'''
Method for multi-threaded file-upload for parallel transfer of very large files (currently only runs on unix systems)
-
+
:param resourceType: resource type for the property
:param resourceId: identifier for the resource
:param localPath: The local path of the file to upload, including file name; local path will not be stored in BaseSpace (use directory argument for this)
:param fileName: The desired filename on the server
:param directory: The desired directory name on the server (empty string will place it in the root directory)
:param contentType: The content type of the file
- :param tempdir: (optional) Temp directory to use for temporary file chunks to upload
:param processCount: (optional) The number of processes to be used, default 10
:param partSize: (optional) The size in MB of individual upload parts (must be >5 Mb and <=25 Mb), default 25
:returns: a File instance, which has been updated after the upload has completed.
@@ -1172,11 +1207,9 @@ def multipartFileUpload(self, resourceType, resourceId, localPath, fileName, dir
# First create file object in BaseSpace, then create multipart upload object and start upload
if partSize <= 5 or partSize > 25:
raise UploadPartSizeException("Multipart upload partSize must be >5 MB and <=25 MB")
- if tempDir is None:
- tempDir = mkdtemp()
bsFile = self.__initiateMultipartFileUpload__(resourceType, resourceId, fileName, directory, contentType)
- myMpu = mpu(self, localPath, bsFile, processCount, partSize, temp_dir=tempDir)
- return myMpu.upload()
+ myMpu = mpu(self, localPath, bsFile, processCount, partSize)
+ return myMpu.upload()
def multipartFileUploadSample(self, Id, localPath, fileName, directory, contentType, tempDir=None, processCount=10, partSize=25):
'''
@@ -1196,7 +1229,10 @@ def multipartFileUploadSample(self, Id, localPath, fileName, directory, contentT
if partSize <= 5 or partSize > 25:
raise UploadPartSizeException("Multipart upload partSize must be >5 MB and <=25 MB")
if tempDir is None:
- tempDir = mkdtemp()
+ if self.tempdir:
+ tempDir = self.tempdir
+ else:
+ tempDir = mkdtemp()
bsFile = self.__initiateMultipartFileUploadSample__(Id, fileName, directory, contentType)
myMpu = mpu(self, localPath, bsFile, processCount, partSize, temp_dir=tempDir)
return myMpu.upload()
@@ -1204,21 +1240,21 @@ def multipartFileUploadSample(self, Id, localPath, fileName, directory, contentT
def fileDownload(self, Id, localDir, byteRange=None, createBsDir=False):
'''
Downloads a BaseSpace file to a local directory, and names the file with the BaseSpace file name.
- If the File has a directory in BaseSpace, it will be re-created locally in the provided localDir
+ If the File has a directory in BaseSpace, it will be re-created locally in the provided localDir
(to disable this, set createBsPath=False).
-
- If the file is large, multi-part download will be used.
-
+
+ If the file is large, multi-part download will be used.
+
Byte-range requests are supported for only small byte ranges (single-part downloads).
Byte-range requests are restricted to a single request of 'start' and 'end' bytes, without support for
negative or empty values for 'start' or 'end'.
-
+
:param Id: The file id
- :param localDir: The local directory to place the file in
+ :param localDir: The local directory to place the file in
:param byteRange: (optional) The byte range of the file to retrieve, provide a 2-element list with start and end byte values
:param createBsDir: (optional) create BaseSpace File's directory inside localDir (default: False)
:raises ByteRangeException: if the provided byte range is invalid
- :returns: a File instance
+ :returns: a File instance
'''
max_retries = 5
multipart_min_file_size = 5000000 # bytes
@@ -1231,7 +1267,7 @@ def fileDownload(self, Id, localDir, byteRange=None, createBsDir=False):
raise ByteRangeException("Byte range must have smaller byte number first")
if rangeSize > multipart_min_file_size:
raise ByteRangeException("Byte range %d larger than maximum allowed size %d" % (rangeSize, multipart_min_file_size))
-
+
bsFile = self.getFileById(Id)
if (bsFile.Size < multipart_min_file_size) or (byteRange and (rangeSize < multipart_min_file_size)):
# append File's directory to local dir, and create this path if it doesn't exist
@@ -1239,7 +1275,7 @@ def fileDownload(self, Id, localDir, byteRange=None, createBsDir=False):
if createBsDir:
localDest = os.path.join(localDir, os.path.dirname(bsFile.Path))
if not os.path.exists(localDest):
- os.makedirs(localDest)
+ os.makedirs(localDest)
attempt = 0
while attempt < max_retries:
try:
@@ -1251,18 +1287,18 @@ def fileDownload(self, Id, localDir, byteRange=None, createBsDir=False):
if attempt == max_retries:
raise ServerResponseException("Max retries exceeded")
return bsFile
- else:
+ else:
return self.multipartFileDownload(Id, localDir, createBsDir=createBsDir)
def __downloadFile__(self, Id, localDir, name, byteRange=None, standaloneRangeFile=False, lock=None): #@ReservedAssignment
'''
- Downloads a BaseSpace file to a local directory.
- Supports byte-range requests; by default will seek() into local file for multipart downloads,
+ Downloads a BaseSpace file to a local directory.
+ Supports byte-range requests; by default will seek() into local file for multipart downloads,
with option to save only range data in standalone file (no seek()).
-
- This method is for downloading relatively small files, eg. < 5 MB.
- For larger files, use multipart download (which uses this method for file parts).
-
+
+ This method is for downloading relatively small files, eg. < 5 MB.
+ For larger files, use multipart download (which uses this method for file parts).
+
:param Id: The file id
:param localDir: The local directory to place the file in
:param name: The name of the local file
@@ -1281,24 +1317,24 @@ def __downloadFile__(self, Id, localDir, name, byteRange=None, standaloneRangeFi
queryParams = {}
headerParams = {}
resourcePath = resourcePath.replace('{Id}', Id)
- queryParams['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
-
+ queryParams['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
+
response = self.apiClient.callAPI(resourcePath, method, queryParams, None, headerParams)
- if response['ResponseStatus'].has_key('ErrorCode'):
+ if 'ErrorCode' in response['ResponseStatus']:
raise Exception('BaseSpace error: ' + str(response['ResponseStatus']['ErrorCode']) + ": " + response['ResponseStatus']['Message'])
-
+
# get the Amazon URL, then do the download; for range requests include
# size to ensure reading until end of data stream. Create local file if
- # it doesn't exist (don't truncate in case other processes from
+ # it doesn't exist (don't truncate in case other processes from
# multipart download also do this)
- req = urllib2.Request(response['Response']['HrefContent'])
+ req = urllib.request.Request(response['Response']['HrefContent'])
filename = os.path.join(localDir, name)
if not os.path.exists(filename):
open(filename, 'a').close()
iter_size = 16*1024 # python default
if len(byteRange):
req.add_header('Range', 'bytes=%s-%s' % (byteRange[0], byteRange[1]))
- flo = urllib2.urlopen(req, timeout=self.getTimeout()) # timeout prevents blocking
+ flo = urllib.request.urlopen(req, timeout=self.getTimeout()) # timeout prevents blocking
totRead = 0
with open(filename, 'r+b', 0) as fp:
if len(byteRange) and standaloneRangeFile == False:
@@ -1325,14 +1361,14 @@ def __downloadFile__(self, Id, localDir, name, byteRange=None, standaloneRangeFi
def multipartFileDownload(self, Id, localDir, processCount=10, partSize=25, createBsDir=False, tempDir=""):
'''
Method for multi-threaded file-download for parallel transfer of very large files (currently only runs on unix systems)
-
- :param Id: The ID of the File to download
+
+ :param Id: The ID of the File to download
:param localDir: The local path in which to store the downloaded file
:param processCount: (optional) The number of processes to be used, default 10
:param partSize: (optional) The size in MB of individual file parts to download, default 25
:param createBsDir: (optional) create BaseSpace File's directory in local_dir, default False
:param tempDir: (optional) Set temp directory to use debug mode, which stores downloaded file chunks in individual files, then completes by 'cat'ing chunks into large file
- :returns: a File instance
+ :returns: a File instance
'''
myMpd = mpd(self, Id, localDir, processCount, partSize, createBsDir, tempDir)
return myMpd.download()
@@ -1340,9 +1376,9 @@ def multipartFileDownload(self, Id, localDir, processCount=10, partSize=25, crea
def fileUrl(self, Id):
'''
** Deprecated in favor of fileS3metadata() **
-
+
Returns URL of file (on S3)
-
+
:param Id: The file id
:raises Exception: if REST API call to BaseSpace server fails
:returns: a URL
@@ -1353,17 +1389,17 @@ def fileUrl(self, Id):
queryParams = {}
headerParams = {}
resourcePath = resourcePath.replace('{Id}', Id)
- queryParams['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
-
+ queryParams['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
+
response = self.apiClient.callAPI(resourcePath, method, queryParams, None, headerParams)
- if response['ResponseStatus'].has_key('ErrorCode'):
- raise Exception('BaseSpace error: ' + str(response['ResponseStatus']['ErrorCode']) + ": " + response['ResponseStatus']['Message'])
+ if 'ErrorCode' in response['ResponseStatus']:
+ raise Exception('BaseSpace error: ' + str(response['ResponseStatus']['ErrorCode']) + ": " + response['ResponseStatus']['Message'])
return response['Response']['HrefContent']
def fileS3metadata(self, Id):
'''
Returns the S3 url and etag (md5 for small files uploaded as a single part) for a BaseSpace file
-
+
:param Id: The file id
:raises Exception: if REST API call to BaseSpace server fails
:returns: Dict with s3 url ('url' key) and etag ('etag' key)
@@ -1375,20 +1411,20 @@ def fileS3metadata(self, Id):
queryParams = {}
headerParams = {}
resourcePath = resourcePath.replace('{Id}', Id)
- queryParams['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
-
+ queryParams['redirect'] = 'meta' # we need to add this parameter to get the Amazon link directly
+
response = self.apiClient.callAPI(resourcePath, method, queryParams,None, headerParams)
- if response['ResponseStatus'].has_key('ErrorCode'):
+ if 'ErrorCode' in response['ResponseStatus']:
raise Exception('BaseSpace error: ' + str(response['ResponseStatus']['ErrorCode']) + ": " + response['ResponseStatus']['Message'])
-
+
# record S3 URL
ret['url'] = response['Response']['HrefContent']
-
+
# TODO should use HEAD call here, instead do small GET range request
- # GET S3 url and record etag
- req = urllib2.Request(response['Response']['HrefContent'])
+ # GET S3 url and record etag
+ req = urllib.request.Request(response['Response']['HrefContent'])
req.add_header('Range', 'bytes=%s-%s' % (0, 1))
- flo = urllib2.urlopen(req, timeout=self.getTimeout()) # timeout prevents blocking
+ flo = urllib.request.urlopen(req, timeout=self.getTimeout()) # timeout prevents blocking
try:
etag = flo.headers['etag']
except KeyError:
@@ -1398,12 +1434,12 @@ def fileS3metadata(self, Id):
etag = etag[1:-1]
ret['etag'] = etag
return ret
-
+
def _validateQueryParameters(self, queryPars):
'''
Initializes and validates Query Parameter arguments
-
- :param queryPars: a QueryParameter object
+
+ :param queryPars: a QueryParameter object
:return: dictionary of query parameters
'''
if queryPars is None:
@@ -1426,7 +1462,7 @@ def __dictionaryToProperties__(self, rawProperties, namespace):
'''
LEGAL_KEY_TYPES = [str, int, float, bool]
propList = []
- for key, value in rawProperties.iteritems():
+ for key, value in six.iteritems(rawProperties):
if type(value) not in LEGAL_KEY_TYPES:
raise IllegalParameterException(type(value), LEGAL_KEY_TYPES)
propName = "%s.%s" % (namespace, key)
@@ -1471,8 +1507,8 @@ def getResourceProperties(self, resourceType, resourceId):
'''
Gets the properties for an arbitrary resource:
- https://developer.basespace.illumina.com/docs/content/documentation/rest-api/api-reference#Properties
-
+ https://developer.basespace.illumina.com/docs/content/documentation/rest-api/api-reference#Properties
+
:param resourceType: resource type for the property
Because of this generic treatment of properties (which is fairly new in BaseSpace)
@@ -1495,3 +1531,28 @@ def getResourceProperties(self, resourceType, resourceId):
headerParams = {}
return self.__singleRequest__(PropertiesResponse.PropertiesResponse,
resourcePath, method, queryParams, headerParams)
+
+ def getApplications(self, queryPars=None):
+ '''
+ Get details about all apps.
+ Note that each app will only have a single entry, even if it has many versions
+ :param queryPars: query parameters
+ :return: list of model.Application.Application objects
+ '''
+ resourcePath = '/applications'
+ method = 'GET'
+ headerParams = {}
+ queryParams = self._validateQueryParameters(queryPars)
+ return self.__listRequest__(Application.Application, resourcePath, method, queryParams, headerParams)
+
+ def getApplicationById(self, Id):
+ '''
+ Get a single app by ID
+ :return: App object
+ :raises: ServerResponseException if there is no such app
+ '''
+ resourcePath = '/applications/%s' % Id
+ method = 'GET'
+ headerParams = {}
+ queryParams = {}
+ return self.__singleRequest__(Application.Application, resourcePath, method, queryParams, headerParams)
diff --git a/src/BaseSpacePy/api/BaseSpaceException.py b/src/BaseSpacePy/api/BaseSpaceException.py
index c4bba4a..1c132a4 100644
--- a/src/BaseSpacePy/api/BaseSpaceException.py
+++ b/src/BaseSpacePy/api/BaseSpaceException.py
@@ -16,31 +16,31 @@ def __init__(self, value,legal):
self.parameter = str(value) + ' is not well-defined, legal options are ' + str(legal)
def __str__(self):
return repr(self.parameter)
-
+
class WrongFiletypeException(Exception):
def __init__(self, filetype):
self.parameter = 'This data request is not available for file ' + str(filetype)
def __str__(self):
return repr(self.parameter)
-
+
class ServerResponseException(Exception):
def __init__(self, value):
self.parameter = 'Error with API server response: ' + value
def __str__(self):
return repr(self.parameter)
-
+
class ModelNotInitializedException(Exception):
def __init__(self,value):
self.parameter = 'The request cannot be completed as model has not been initialized - ' + value
def __str__(self):
return repr(self.parameter)
-
+
class ByteRangeException(Exception):
def __init__(self, value):
self.parameter = 'Byte-range invalid: ' + value
def __str__(self):
return repr(self.parameter)
-
+
class MultiProcessingTaskFailedException(Exception):
def __init__(self, value):
self.parameter = 'Multiprocessing task failed: ' + value
@@ -49,43 +49,48 @@ def __str__(self):
class UploadPartSizeException(Exception):
def __init__(self, value):
- self.parameter = 'Upload part size invalid: ' + value
+ self.parameter = 'Upload part size is invalid: ' + value
def __str__(self):
return repr(self.parameter)
class CredentialsException(Exception):
def __init__(self, value):
- self.parameter = 'Error with BaseSpace credentials: ' + value
+ self.parameter = 'Invalid BaseSpace credentials: ' + value
def __str__(self):
return repr(self.parameter)
class QueryParameterException(Exception):
def __init__(self, value):
- self.parameter = 'Error with query parameter: ' + value
+ self.parameter = 'Invalid query parameter: ' + value
def __str__(self):
return repr(self.parameter)
class AppSessionException(Exception):
def __init__(self, value):
- self.parameter = 'Error with AppSession: ' + value
+ self.parameter = 'AppSession error: ' + value
def __str__(self):
return repr(self.parameter)
class ModelNotSupportedException(Exception):
def __init__(self, value):
- self.parameter = 'Model not supported: ' + value
+ self.parameter = 'Unsupported model: ' + value
def __str__(self):
return repr(self.parameter)
class OAuthException(Exception):
def __init__(self, value):
- self.parameter = 'Error with OAuth: ' + value
+ self.parameter = 'Could not authenticate with OAuth: ' + value
def __str__(self):
return repr(self.parameter)
class RestMethodException(Exception):
def __init__(self, value):
- self.parameter = 'Error with REST API method: ' + value
+ self.parameter = 'Problem with REST API method: ' + value
+ def __str__(self):
+ return repr(self.parameter)
+
+class DownloadFailedException(Exception):
+ def __init__(self, value):
+ self.parameter = 'Download failed: ' + value
def __str__(self):
return repr(self.parameter)
-
diff --git a/src/BaseSpacePy/api/BillingAPI.py b/src/BaseSpacePy/api/BillingAPI.py
index d1af30a..804f8b4 100644
--- a/src/BaseSpacePy/api/BillingAPI.py
+++ b/src/BaseSpacePy/api/BillingAPI.py
@@ -1,10 +1,12 @@
-import urlparse
from BaseSpacePy.api.BaseAPI import BaseAPI
from BaseSpacePy.api.BaseSpaceException import * #@UnusedWildImport
from BaseSpacePy.model import * #@UnusedWildImport
from BaseSpacePy.model.QueryParametersPurchasedProduct import QueryParametersPurchasedProduct as qpp
+from six.moves import urllib
+
+
class BillingAPI(BaseAPI):
'''
The API class used for all communication with the BaseSpace Billng server
@@ -18,7 +20,7 @@ def __init__(self, apiServer, version, appSessionId='', AccessToken=''):
'''
self.appSessionId = appSessionId
self.version = version
- apiServerAndVersion = urlparse.urljoin(apiServer, version)
+ apiServerAndVersion = urllib.parse.urljoin(apiServer, version)
super(BillingAPI, self).__init__(AccessToken, apiServerAndVersion)
def createPurchase(self, products, appSessionId=''):
diff --git a/src/BaseSpacePy/model/Application.py b/src/BaseSpacePy/model/Application.py
index d2b4f02..12a4ac9 100644
--- a/src/BaseSpacePy/model/Application.py
+++ b/src/BaseSpacePy/model/Application.py
@@ -9,5 +9,6 @@ def __init__(self):
'HrefLogo': 'str',
'HomepageUri': 'str',
'ShortDescription': 'str',
- 'DateCreated': 'datetime'
+ 'DateCreated': 'datetime',
+ 'VersionNumber': 'str'
}
diff --git a/src/BaseSpacePy/model/KeyValues.py b/src/BaseSpacePy/model/KeyValues.py
new file mode 100644
index 0000000..c211e5b
--- /dev/null
+++ b/src/BaseSpacePy/model/KeyValues.py
@@ -0,0 +1,13 @@
+class KeyValues(object):
+
+ def __init__(self):
+ self.swaggerTypes = {
+ 'Key': 'str',
+ 'Values': 'list',
+ }
+
+ def __str__(self):
+ return str(self.Key)
+
+ def __repr__(self):
+ return str(self)
diff --git a/src/BaseSpacePy/model/ListResponse.py b/src/BaseSpacePy/model/ListResponse.py
index edbf7f1..63d252c 100644
--- a/src/BaseSpacePy/model/ListResponse.py
+++ b/src/BaseSpacePy/model/ListResponse.py
@@ -1,6 +1,8 @@
import json
-from StringIO import StringIO
+
+from six import StringIO
+
class ListResponse(object):
diff --git a/src/BaseSpacePy/model/MultipartFileTransfer.py b/src/BaseSpacePy/model/MultipartFileTransfer.py
index 4555a37..8a4565c 100644
--- a/src/BaseSpacePy/model/MultipartFileTransfer.py
+++ b/src/BaseSpacePy/model/MultipartFileTransfer.py
@@ -3,85 +3,78 @@
import os
import math
import multiprocessing
-import Queue
import shutil
+import io
import signal
import hashlib
from subprocess import call
import logging
+import base64
from BaseSpacePy.api.BaseSpaceException import MultiProcessingTaskFailedException
+from six.moves import queue
+from six.moves import range
+
+
LOGGER = logging.getLogger(__name__)
+
class UploadTask(object):
'''
- Uploads a piece of a large local file.
- '''
- def __init__(self, api, bs_file_id, piece, total_pieces, local_path, total_size, temp_dir):
+ Uploads a piece of a large local file.
+ '''
+ def __init__(self, api, bs_file_id, piece, total_pieces, local_path, total_size, chunk_size):
self.api = api
self.bs_file_id = bs_file_id # the BaseSpace File Id
- self.piece = piece # piece number
+ self.piece = piece # piece number
self.total_pieces = total_pieces # out of total piece count
- self.local_path = local_path # the path of the local file to be uploaded, including file name
+ self.local_path = local_path # the path of the local file to be uploaded, including file name
self.total_size = total_size # total file size of upload, for reporting
- self.temp_dir = temp_dir # temp location to store file chunks for upload
-
+ self.chunk_size = chunk_size # chunk size
+
# tasks must implement these attributes and execute()
self.success = False
- self.err_msg = "no error"
-
+ self.err_msg = "no error"
+
def execute(self, lock):
'''
- Upload a piece of the target file, first splitting the local file into a temp file.
Calculate md5 of file piece and pass to upload method.
Lock is not used (but needed since worker sends it for multipart download)
- '''
+ '''
try:
fname = os.path.basename(self.local_path)
- # this relies on the way the calling function has split the file
- # but we still need to pass around the piece numbers because the BaseSpace API needs them
- # to reassemble the file at the other end
- # the zfill(4) is to make sure we have a zero padded suffix that split -a 4 -d will make
- transFile = os.path.join(self.temp_dir, fname + str(self.piece).zfill(4))
- #cmd = ['split', '-d', '-n', str(self.piece) + '/' + str(self.total_pieces), self.local_path]
- #with open(transFile, "w") as fp:
- # rc = call(cmd, stdout=fp)
- # if rc != 0:
- # self.sucess = False
- # self.err_msg = "Splitting local file failed for piece %s" % str(self.piece)
- # return self
- with open(transFile, "r") as f:
- out = f.read()
- self.md5 = hashlib.md5(out).digest().encode('base64')
+ chunk_data = ""
+ with io.open(self.local_path, "rb") as fh:
+ fh.seek(self.piece * self.chunk_size)
+ chunk_data = fh.read(self.chunk_size)
+ self.md5 = base64.b64encode(hashlib.md5(chunk_data).digest())
try:
- res = self.api.__uploadMultipartUnit__(self.bs_file_id,self.piece+1,self.md5,transFile)
+ res = self.api.__uploadMultipartUnit__(self.bs_file_id,self.piece+1,self.md5,chunk_data)
except Exception as e:
self.success = False
- self.err_msg = str(e)
+ self.err_msg = str(e)
else:
# ETag contains hex encoded MD5 of part data on success
- if res and res['Response'].has_key('ETag'):
+ if res and 'ETag' in res['Response']:
self.success = True
else:
self.success = False
self.err_msg = "Error - empty response from uploading file piece or missing ETag in response"
- if self.success:
- os.remove(transFile)
# capture exception, since unpickleable exceptions may block
except Exception as e:
self.success = False
self.err_msg = str(e)
return self
-
+
def __str__(self):
- return 'File piece %d of %d, total file size %s' % (self.piece, self.total_pieces, Utils.readable_bytes(self.total_size))
+ return 'File piece %d of %d, total %s' % (self.piece+1, self.total_pieces, Utils.readable_bytes(self.total_size))
class DownloadTask(object):
'''
Downloads a piece of a large remote file.
When temp_dir is set (debug mode), downloads to filename with piece number appended (i.e. temp file).
- '''
+ '''
def __init__(self, api, bs_file_id, file_name, local_dir, piece, total_pieces, part_size, total_size, temp_dir=None):
self.api = api # BaseSpace api object
self.bs_file_id = bs_file_id # the Id of the File in BaseSpace
@@ -90,13 +83,13 @@ def __init__(self, api, bs_file_id, file_name, local_dir, piece, total_pieces, p
self.total_pieces = total_pieces # total pieces being downloaded (for reporting only)
self.part_size = part_size # the size in bytes (not MB) of each piece (except last piece)
self.total_size = total_size # the total size of the file in bytes
- self.local_dir = local_dir # the path in which to store the downloaded file
- self.temp_dir = temp_dir # optional: set temp_dir for debug mode, which writes downloaded chunks to individual temp files
-
+ self.local_dir = local_dir # the path in which to store the downloaded file
+ self.temp_dir = temp_dir # optional: set temp_dir for debug mode, which writes downloaded chunks to individual temp files
+
# tasks must implement these attributes and execute()
self.success = False
- self.err_msg = "no error"
-
+ self.err_msg = "no error"
+
def execute(self, lock):
'''
Download a piece of the target file, first calculating start/end bytes for piece.
@@ -116,146 +109,147 @@ def execute(self, lock):
startbyte = (self.piece - 1) * self.part_size
endbyte = (self.piece * self.part_size) - 1
if endbyte > self.total_size:
- endbyte = self.total_size - 1
- try:
+ endbyte = self.total_size - 1
+ try:
#self.api.__downloadFile__(self.bs_file_id, self.local_dir, transFile, [startbyte, endbyte], standaloneRangeFile, lock)
- self.api.__downloadFile__(self.bs_file_id, local_dir, local_name, [startbyte, endbyte], standaloneRangeFile, lock)
+ self.api.__downloadFile__(self.bs_file_id, local_dir, local_name, [startbyte, endbyte], standaloneRangeFile, lock)
except Exception as e:
self.success = False
- self.err_msg = str(e)
- else:
+ self.err_msg = str(e)
+ else:
self.success = True
# capture exception, since unpickleable exceptions may block
except Exception as e:
self.success = False
self.err_msg = str(e)
return self
-
- def __str__(self):
+
+ def __str__(self):
return 'File piece %d of %d, piece size %s of total %s' % (self.piece, self.total_pieces, Utils.readable_bytes(self.part_size), Utils.readable_bytes(self.total_size))
-
+
class Consumer(multiprocessing.Process):
'''
Multi-processing worker that executes tasks from task queue with retry
On failure after retries, alerts all workers to halt
'''
-
- def __init__(self, task_queue, result_queue, halt_event, lock):
+
+ def __init__(self, task_queue, result_queue, halt_event, lock):
multiprocessing.Process.__init__(self)
self.task_queue = task_queue
- self.result_queue = result_queue
+ self.result_queue = result_queue
self.halt = halt_event
- self.lock = lock
-
+ self.lock = lock
+
self.get_task_timeout = 5 # secs
self.retry_wait = 1 # sec
self.retries = 20
-
+
def run(self):
'''
- Executes tasks from the task queue until poison pill is reached, halt
+ Executes tasks from the task queue until poison pill is reached, halt
signal is found, or something went wrong such as a timeout when getting
- new tasks.
-
+ new tasks.
+
For download tasks, use lock to ensure sole access (among worker
processes) to downloaded file.
-
+
Retries failed tasks, and add task results to result_queue.
When a task fails for all retries, set halt signal to alert other workers
and purge task queue or remaining tasks (to unblock join() in parent process)
-
+
Turn off SIGINT (Ctrl C), handle in parent process
'''
signal.signal(signal.SIGINT, signal.SIG_IGN)
- while True:
+ while True:
try:
next_task = self.task_queue.get(True, self.get_task_timeout) # block until timeout
- except Queue.Empty:
+ except queue.Empty:
LOGGER.debug('Worker %s exiting, getting task from task queue timed out and/or is empty' % self.name)
- break
- if next_task is None:
+ break
+ if next_task is None:
LOGGER.debug('Worker %s exiting, found final task' % self.name)
self.task_queue.task_done()
- break
- else:
+ break
+ else:
# attempt to run tasks, with retry
LOGGER.debug('Worker %s processing task: %s' % (self.name, str(next_task)))
- for i in xrange(1, self.retries + 1):
+ LOGGER.info('%s' % str(next_task))
+ for i in range(1, self.retries + 1):
if self.halt.is_set():
LOGGER.debug('Worker %s exiting, found halt signal' % self.name)
self.task_queue.task_done()
self.purge_task_queue()
- return
- answer = next_task.execute(self.lock) # acquired lock will block other workers
+ return
+ answer = next_task.execute(self.lock) # acquired lock will block other workers
if answer.success == True:
- self.task_queue.task_done()
+ self.task_queue.task_done()
self.result_queue.put(True)
break
else:
LOGGER.debug("Worker %s retrying task %s after failure, retry attempt %d, with error msg: %s" % (self.name, str(next_task), i, answer.err_msg))
- time.sleep(self.retry_wait)
+ time.sleep(self.retry_wait)
if not answer.success == True:
LOGGER.debug("Worker %s exiting, too many failures with retry for worker %s" % (self.name, str(self)))
- LOGGER.warning("Task failed after too many retries")
- self.task_queue.task_done()
+ LOGGER.warning("Task failed after too many retries")
+ self.task_queue.task_done()
self.result_queue.put(False)
- self.purge_task_queue() # purge task queue in case there's only one worker
+ self.purge_task_queue() # purge task queue in case there's only one worker
self.halt.set()
- break
+ break
return
-
+
def purge_task_queue(self):
'''
Purge all remaining tasks from task queue. This will also remove poison pills
(final tasks), so run() must handle an empty queue (by using a timeout with
task_queue.get() ).
- '''
+ '''
LOGGER.debug("Purging task queue")
while 1:
try:
- self.task_queue.get(False)
- except Queue.Empty:
+ self.task_queue.get(False)
+ except queue.Empty:
break
else:
self.task_queue.task_done()
-
+
class Executor(object):
'''
Multi-processing task manager, with callback to finalize once workers are completed.
Task queue contains tasks, with poison pill for each worker.
Result queue contains True/False results for task success/failure.
Halt event will tell workers to halt themselves.
-
- For downloads, lock is to ensure that only one worker writes to a local downloaded file at a time.
+
+ For downloads, lock is to ensure that only one worker writes to a local downloaded file at a time.
'''
- def __init__(self):
+ def __init__(self):
self.tasks = multiprocessing.JoinableQueue()
- self.result_queue = multiprocessing.Queue()
+ self.result_queue = multiprocessing.Queue()
self.halt_event = multiprocessing.Event()
self.lock = multiprocessing.Lock()
-
+
def add_task(self, task):
'''
Add task to task queue
'''
- self.tasks.put(task)
-
+ self.tasks.put(task)
+
def add_workers(self, num_workers):
'''
Added workers to internal list of workers, adding a poison pill for each to the task queue
'''
- self.consumers = [ Consumer(self.tasks, self.result_queue, self.halt_event, self.lock) for i in xrange(num_workers) ]
+ self.consumers = [ Consumer(self.tasks, self.result_queue, self.halt_event, self.lock) for i in range(num_workers) ]
for c in self.consumers:
self.tasks.put(None)
def start_workers(self, finalize_callback):
'''
Start workers, wait until workers finish, then call finalize callback if all went well
- '''
+ '''
# TODO add failure callback for cleanup?
for w in self.consumers:
- w.start()
- LOGGER.debug("Workers started")
+ w.start()
+ LOGGER.debug("Workers started")
try:
self.tasks.join()
except (KeyboardInterrupt, SystemExit):
@@ -263,103 +257,100 @@ def start_workers(self, finalize_callback):
self.result_queue.put(False)
self.halt_event.set()
self.tasks.join() # wait for workers to finish current work then exit from response to halt signal
- else:
- LOGGER.debug("Workers finished - task queue joined")
+ else:
+ LOGGER.debug("Workers finished - task queue joined")
finalize = True
while 1:
try:
- success = self.result_queue.get(False) # non-blocking
- except Queue.Empty:
+ success = self.result_queue.get(False) # non-blocking
+ except queue.Empty:
break
- else:
- if success == False:
+ else:
+ if success == False:
LOGGER.debug("Found a failed or cancelled task -- won't call finalize callback")
- finalize = False
- if finalize == True:
+ finalize = False
+ if finalize == True:
finalize_callback()
- else:
- raise MultiProcessingTaskFailedException("Multiprocessing task did not complete successfully")
+ else:
+ raise MultiProcessingTaskFailedException("Multiprocessing task did not complete successfully")
class MultipartUpload(object):
'''
- Uploads a (large) file by uploading file parts in separate processes.
+ Uploads a (large) file by uploading file parts in separate processes.
'''
- def __init__(self, api, local_path, bs_file, process_count, part_size, temp_dir):
+ def __init__(self, api, local_path, bs_file, process_count, part_size, logger=None):
'''
Create a multipart upload object
-
- :param api: the BaseSpace API object
+
+ :param api: the BaseSpace API object
:param local_path: the path of the local file, including file name
- :param bs_file: the File object of the newly created BaseSpace File to upload
+ :param bs_file: the File object of the newly created BaseSpace File to upload
:param process_count: the number of process to use for uploading
- :param part_size: in MB, the size of each uploaded part
- :param temp_dir: temp directory to store file pieces for upload
+ :param part_size: in MB, the size of each uploaded part
'''
- self.api = api
- self.local_path = local_path
+ self.api = api
+ self.local_path = local_path
self.remote_file = bs_file
self.process_count = process_count
self.part_size = part_size
- self.temp_dir = temp_dir
-
+
self.start_chunk = 0
-
+
def upload(self):
'''
Start the upload, then when complete retrieve and return the file object from
BaseSpace that has updated (completed) attributes.
'''
self._setup()
- self._start_workers()
- return self.api.getFileById(self.remote_file.Id)
-
- def _setup(self):
- '''
- Determine number of file pieces to upload, add upload tasks to work queue
- '''
- logfile = os.path.join(self.temp_dir, "main.log")
- total_size = os.path.getsize(self.local_path)
- fileCount = int(total_size/(self.part_size*1024*1024)) + 1
-
- chunk_size = (total_size / fileCount) + 1
+ self._start_workers()
+ return self.api.getFileById(self.remote_file.Id)
+
+ def _setup(self):
+ '''
+ Determine number of file pieces to upload, add upload tasks to work queue
+ '''
+ total_size = os.path.getsize(self.local_path)
+ # round up to get a number of chunks that will be enough for the whole file
+ fileCount = int(math.ceil(total_size/float(self.part_size*1024*1024)))
+ chunk_size = self.part_size*1024*1024
assert chunk_size * fileCount > total_size
- fname = os.path.basename(self.local_path)
- prefix = os.path.join(self.temp_dir, fname)
+ # fname = os.path.basename(self.local_path)
+ # prefix = os.path.join(self.temp_dir, fname)
# -a 4 always use 4 digit sufixes, to make sure we can predict the filenames
# -d use digits as suffixes, not letters
# -b chunk size (in bytes)
- cmd = ['split', '-a', '4', '-d', '-b', str(chunk_size), self.local_path, prefix]
- rc = call(cmd)
- if rc != 0:
- err_msg = "Splitting local file failed: %s" % str.local_path
- raise MultiProcessingTaskFailedException(err_msg)
-
- self.exe = Executor()
- for i in xrange(self.start_chunk, fileCount):
- t = UploadTask(self.api, self.remote_file.Id, i, fileCount, self.local_path, total_size, self.temp_dir)
- self.exe.add_task(t)
+ # cmd = ['split', '-a', '4', '-d', '-b', str(chunk_size), self.local_path, prefix]
+ # rc = call(cmd)
+ # if rc != 0:
+ # err_msg = "Splitting local file failed: %s" % self.local_path
+ # raise MultiProcessingTaskFailedException(err_msg)
+
+ self.exe = Executor()
+ for i in range(self.start_chunk, fileCount):
+ t = UploadTask(self.api, self.remote_file.Id, i, fileCount, self.local_path, total_size, chunk_size)
+ self.exe.add_task(t)
self.exe.add_workers(self.process_count)
- self.task_total = fileCount - self.start_chunk + 1
+ self.task_total = fileCount - self.start_chunk + 1
LOGGER.info("Total File Size %s" % Utils.readable_bytes(total_size))
LOGGER.info("Using File Part Size %d MB" % self.part_size)
- LOGGER.info("Processes %d" % self.process_count)
- LOGGER.info("File Chunk Count %d" % self.task_total)
- LOGGER.info("Start Chunk %d" % self.start_chunk)
+ LOGGER.debug("Processes %d" % self.process_count)
+ LOGGER.debug("File Chunk Count %d" % self.task_total)
+ LOGGER.debug("Start Chunk %d" % self.start_chunk)
def _start_workers(self):
'''
Start upload workers, register finalize callback method
- '''
- finalize_callback = self._finalize_upload # lambda: None
+ '''
+ finalize_callback = self._finalize_upload # lambda: None
self.exe.start_workers(finalize_callback)
-
+
def _finalize_upload(self):
'''
Set file upload status as complete in BaseSpace
'''
- LOGGER.debug("Marking uploaded file status as complete")
+ LOGGER.debug("Marking uploaded file status as complete")
self.api.__finalizeMultipartFileUpload__(self.remote_file.Id)
class MultipartDownload(object):
@@ -371,105 +362,105 @@ class MultipartDownload(object):
def __init__(self, api, file_id, local_dir, process_count, part_size, create_bs_dir, temp_dir=""):
'''
Create a multipart download object
-
+
:param api: the BaseSpace API object
:param file_id: the BaseSpace File Id of the file to download
:param local_dir: the local directory in which to store the downloaded file
:param process_count: the number of process to use for downloading
- :param part_size: in MB, the size of each file part to download
+ :param part_size: in MB, the size of each file part to download
:param create_bs_dir: when True, create BaseSpace File's directory in local_dir; when False, ignore Bs directory
- :param temp_dir: (optional) temp directory for debug mode
+ :param temp_dir: (optional) temp directory for debug mode
'''
- self.api = api
- self.file_id = file_id
- self.local_dir = local_dir
- self.process_count = process_count
- self.part_size = part_size
+ self.api = api
+ self.file_id = file_id
+ self.local_dir = local_dir
+ self.process_count = process_count
+ self.part_size = part_size
self.temp_dir = temp_dir
- self.create_bs_dir = create_bs_dir
+ self.create_bs_dir = create_bs_dir
- self.start_chunk = 1
+ self.start_chunk = 1
self.partial_file_ext = ".partial"
-
+
def download(self):
'''
Start the download
'''
self._setup()
self._start_workers()
- return self.bs_file
-
+ return self.bs_file
+
def _setup(self):
'''
Determine number of file pieces to download, determine full local path
in which to download file, add download tasks to work queue.
-
- While download is in progress, name the file with a 'partial' extension
+
+ While download is in progress, name the file with a 'partial' extension
'''
self.bs_file = self.api.getFileById(self.file_id)
self.file_name = self.bs_file.Name
total_bytes = self.bs_file.Size
part_size_bytes = self.part_size * (1024**2)
- self.file_count = int(math.ceil(total_bytes/part_size_bytes)) + 1
-
+ self.file_count = int(math.ceil(total_bytes/float(part_size_bytes)))
+
file_name = self.file_name
if not self.temp_dir:
file_name = self.file_name + self.partial_file_ext
self.full_local_dir = self.local_dir
self.full_temp_dir = self.temp_dir
- if self.create_bs_dir:
- self.full_local_dir = os.path.join(self.local_dir, os.path.dirname(self.bs_file.Path))
+ if self.create_bs_dir:
+ self.full_local_dir = os.path.join(self.local_dir, os.path.dirname(self.bs_file.Path))
if not os.path.exists(self.full_local_dir):
os.makedirs(self.full_local_dir)
if self.temp_dir:
self.full_temp_dir = os.path.join(self.temp_dir, os.path.dirname(self.bs_file.Path))
if not os.path.exists(self.full_temp_dir):
os.makedirs(self.full_temp_dir)
-
- self.exe = Executor()
- for i in xrange(self.start_chunk, self.file_count+1):
- t = DownloadTask(self.api, self.file_id, file_name, self.full_local_dir,
+
+ self.exe = Executor()
+ for i in range(self.start_chunk, self.file_count+1):
+ t = DownloadTask(self.api, self.file_id, file_name, self.full_local_dir,
i, self.file_count, part_size_bytes, total_bytes, self.full_temp_dir)
- self.exe.add_task(t)
- self.exe.add_workers(self.process_count)
- self.task_total = self.file_count - self.start_chunk + 1
-
- LOGGER.info("Total File Size %s" % Utils.readable_bytes(total_bytes))
- LOGGER.info("Using File Part Size %s MB" % str(self.part_size))
- LOGGER.info("Processes %d" % self.process_count)
- LOGGER.info("File Chunk Count %d" % self.file_count)
- LOGGER.info("Start Chunk %d" % self.start_chunk)
-
+ self.exe.add_task(t)
+ self.exe.add_workers(self.process_count)
+ self.task_total = self.file_count - self.start_chunk + 1
+
+ LOGGER.debug("Total File Size %s" % Utils.readable_bytes(total_bytes))
+ LOGGER.debug("Using File Part Size %s MB" % str(self.part_size))
+ LOGGER.debug("Processes %d" % self.process_count)
+ LOGGER.debug("File Chunk Count %d" % self.file_count)
+ LOGGER.debug("Start Chunk %d" % self.start_chunk)
+
def _start_workers(self):
'''
Start download workers, register finalize callback method
- '''
+ '''
if self.temp_dir:
finalize_callback = self._combine_file_chunks
else:
- finalize_callback = self._rename_final_file # lambda: None
- self.exe.start_workers(finalize_callback)
-
+ finalize_callback = self._rename_final_file # lambda: None
+ self.exe.start_workers(finalize_callback)
+
def _rename_final_file(self):
'''
Remove the 'partial' extension from the downloaded file
'''
final_file = os.path.join(self.full_local_dir, self.file_name)
partial_file = final_file + self.partial_file_ext
- os.rename(partial_file, final_file)
-
+ os.rename(partial_file, final_file)
+
def _combine_file_chunks(self):
'''
Assembles download files chunks into single large file, then cleanup by deleting file chunks
- '''
- LOGGER.debug("Assembling downloaded file parts into single file")
- part_files = [os.path.join(self.full_temp_dir, self.file_name + '.' + str(i)) for i in xrange(self.start_chunk, self.file_count+1)]
+ '''
+ LOGGER.debug("Assembling downloaded file parts into single file")
+ part_files = [os.path.join(self.full_temp_dir, self.file_name + '.' + str(i)) for i in range(self.start_chunk, self.file_count+1)]
with open(os.path.join(self.full_local_dir, self.file_name), 'w+b') as whole_file:
- for part_file in part_files:
- shutil.copyfileobj(open(part_file, 'r+b'), whole_file)
+ for part_file in part_files:
+ shutil.copyfileobj(open(part_file, 'r+b'), whole_file)
for part_file in part_files:
- os.remove(part_file)
+ os.remove(part_file)
class Utils(object):
'''
@@ -487,7 +478,7 @@ def md5_for_file(f, block_size=1024*1024):
break
md5.update(data)
return md5.hexdigest()
-
+
@staticmethod
def readable_bytes(size, precision=2):
"""
@@ -499,4 +490,3 @@ def readable_bytes(size, precision=2):
suffixIndex += 1 # increment the index of the suffix
size = size / 1024.0 # apply the division
return "%.*f %s" % (precision, size, suffixes[suffixIndex])
-
\ No newline at end of file
diff --git a/src/BaseSpacePy/model/Project.py b/src/BaseSpacePy/model/Project.py
index 80f5cef..75b6514 100644
--- a/src/BaseSpacePy/model/Project.py
+++ b/src/BaseSpacePy/model/Project.py
@@ -58,10 +58,11 @@ def getAppResults(self, api, queryPars=None, statuses=None):
:param api: An instance of BaseSpaceAPI
:param queryPars: An (optional) object of type QueryParameters for custom sorting and filtering
:param statuses: An optional list of statuses, eg. 'complete'
+ :return: list of AppResult objects
'''
self.isInit()
return api.getAppResultsByProject(self.Id, queryPars=queryPars, statuses=statuses)
-
+
def getSamples(self, api, queryPars=None):
'''
Returns a list of Sample objects.
diff --git a/src/BaseSpacePy/model/PropertyMap.py b/src/BaseSpacePy/model/PropertyMap.py
index 279bba7..a1344ab 100644
--- a/src/BaseSpacePy/model/PropertyMap.py
+++ b/src/BaseSpacePy/model/PropertyMap.py
@@ -7,7 +7,7 @@ def __init__(self):
'Href': 'str',
'Name': 'str',
'Description': 'str',
- 'Items': 'list',
+ 'Content': 'list',
}
def __str__(self):
diff --git a/src/BaseSpacePy/model/QueryParameters.py b/src/BaseSpacePy/model/QueryParameters.py
index ddf833e..2b23e28 100644
--- a/src/BaseSpacePy/model/QueryParameters.py
+++ b/src/BaseSpacePy/model/QueryParameters.py
@@ -1,69 +1,73 @@
-
+import six
from BaseSpacePy.api.BaseSpaceException import UndefinedParameterException, UnknownParameterException, IllegalParameterException, QueryParameterException
legal = {'Statuses': [],
+ 'Status':[],
'SortBy': ['Id', 'Name', 'DateCreated', 'Path', 'Position'],
'Extensions': [],
#'Extensions': ['bam', 'vcf'],
'Offset': [],
'Limit': [],
- 'SortDir': ['Asc', 'Desc'],
- 'Name': [],
- 'StartPos':[],
- 'EndPos':[],
- 'Format':[]
- #'Format': ['txt', 'json', 'vcf'],
+ 'SortDir': ['Asc', 'Desc'],
+ 'Name': [],
+ 'StartPos':[],
+ 'EndPos':[],
+ 'Format':[],
+ 'include':[],
+ 'propertyFilters':[],
+ 'userCreatedBy':[]
+ #'Format': ['txt', 'json', 'vcf'],
}
class QueryParameters(object):
'''
The QueryParameters class can be passed as an optional argument
- for sorting/filtering of list-responses (such as lists of samples, AppResults, variants, etc.)
+ for sorting/filtering of list-responses (such as lists of samples, AppResults, variants, etc.)
'''
def __init__(self, pars=None, required=None):
'''
:param pars: (optional) a dictionary of query parameters, default None
:param required: (optional) a list of required query parameter names, default None
-
+
:raises QueryParameterException: when non-dictionary argument for 'pars' is passed
'''
if pars is None:
pars = {}
if required is None:
- required = []
+ required = []
self.passed = {}
try:
- for k in pars.keys():
+ for k in six.iterkeys(pars):
self.passed[k] = pars[k]
except AttributeError:
raise QueryParameterException("The 'pars' argument to QueryParameters must be a dictionary")
self.required = required
-
+
def __str__(self):
return str(self.passed)
-
+
def __repr__(self):
return str(self)
-
+
def getParameterDict(self):
return self.passed
-
+
def validate(self):
'''
Validates that query parameter keys and values are properly formed:
- required keys are present, and keys and values are within the set of
+ required keys are present, and keys and values are within the set of
known acceptable keys/values.
-
+
:raises UndefinedParameterException: when a required parameter is not present
:raises UnknownParameterException: when a parameter name is not present in the list of acceptable parameters names
:raises IllegalParameterException: when a parameter value (with a valid name) is not present in the list of acceptable parameters values
:returns: None
'''
for p in self.required:
- if not self.passed.has_key(p):
+ if not p in self.passed:
raise UndefinedParameterException(p)
- for p in self.passed.keys():
- if not legal.has_key(p):
+ for p in six.iterkeys(self.passed):
+ if not p in legal:
raise UnknownParameterException(p)
- if len(legal[p])>0 and (not self.passed[p] in legal[p]):
+ if len(legal[p])>0 and (not self.passed[p] in legal[p]):
raise IllegalParameterException(p,legal[p])
diff --git a/src/BaseSpacePy/model/QueryParametersPurchasedProduct.py b/src/BaseSpacePy/model/QueryParametersPurchasedProduct.py
index 44279d3..f25bd7d 100644
--- a/src/BaseSpacePy/model/QueryParametersPurchasedProduct.py
+++ b/src/BaseSpacePy/model/QueryParametersPurchasedProduct.py
@@ -1,4 +1,4 @@
-
+import six
from BaseSpacePy.api.BaseSpaceException import UndefinedParameterException,UnknownParameterException,IllegalParameterException
legal = { 'Tags':[], 'ProductIds':[] }
@@ -11,20 +11,20 @@ def __init__(self, pars=None):
if pars is None:
pars = {}
self.passed = {}
- for k in pars.keys():
+ for k in six.iterkeys(pars):
self.passed[k] = pars[k]
self.validate()
-
+
def __str__(self):
return str(self.passed)
-
+
def __repr__(self):
return str(self)
-
+
def getParameterDict(self):
return self.passed
-
+
def validate(self):
- for p in self.passed.keys():
- if not legal.has_key(p):
+ for p in six.iterkeys(self.passed):
+ if not p in legal:
raise UnknownParameterException(p)
diff --git a/src/BaseSpacePy/model/Token.py b/src/BaseSpacePy/model/Token.py
new file mode 100644
index 0000000..55ea5f9
--- /dev/null
+++ b/src/BaseSpacePy/model/Token.py
@@ -0,0 +1,10 @@
+class Token(object):
+ def __init__(self):
+ self.swaggerTypes = {
+ 'Scopes': 'list',
+ 'DateCreated': 'datetime',
+ 'UserResourceOwner': 'User',
+ 'Application': 'Application',
+ 'AccessToken': 'str'
+ }
+
diff --git a/src/BaseSpacePy/model/__init__.py b/src/BaseSpacePy/model/__init__.py
index f78aadf..7259b23 100644
--- a/src/BaseSpacePy/model/__init__.py
+++ b/src/BaseSpacePy/model/__init__.py
@@ -1,5 +1,6 @@
__all__= [
+ 'Token',
'ListResponse',
'ResponseStatus',
'File',
@@ -72,4 +73,5 @@
'RunResponse',
'Run',
'MultipartFileTransfer',
+ 'KeyValues',
]
diff --git a/src/setup.cfg b/src/setup.cfg
new file mode 100644
index 0000000..0ac0f47
--- /dev/null
+++ b/src/setup.cfg
@@ -0,0 +1,6 @@
+[bdist_rpm]
+
+requires = python >= 2.6
+ python-dateutil
+ python-requests
+no-autoreq = yes
diff --git a/src/setup.py b/src/setup.py
index d98dca5..f79100d 100755
--- a/src/setup.py
+++ b/src/setup.py
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,30 +21,25 @@
from distutils.core import setup
-setup(name='BaseSpacePy',
+setup(name='basespace-python-sdk',
description='A Python SDK for connecting to Illumina BaseSpace data',
author='Illumina',
- version='0.3',
+ version='0.5.1',
long_description="""
BaseSpacePy is a Python based SDK to be used in the development of Apps and scripts for working with
Illumina's BaseSpace cloud-computing solution for next-gen sequencing data analysis.
The primary purpose of the SDK is to provide an easy-to-use Python environment enabling developers
to authenticate a user, retrieve data, and upload data/results from their own analysis to BaseSpace.""",
- author_email='',
+ author_email='techsupport@illumina.com',
packages=['BaseSpacePy.api','BaseSpacePy.model','BaseSpacePy'],
package_dir={'BaseSpacePy' : os.path.join(os.path.dirname(__file__),'BaseSpacePy')},
- requires=['pycurl','dateutil'],
+ install_requires=['python-dateutil','requests','six','configparser'],
zip_safe=False,
)
# Warn use if dependent packages aren't installed
#try:
-# import pycurl
-#except:
-# print "WARNING - please install required package 'pycurl'"
-#try:
# import dateutil
#except:
-# print "WARNING - please install required package 'python-dateutil'"
-
+# print("WARNING - please install required package 'python-dateutil'")
diff --git a/test/dotbasespace/completion.bash b/test/dotbasespace/completion.bash
new file mode 100644
index 0000000..2320be0
--- /dev/null
+++ b/test/dotbasespace/completion.bash
@@ -0,0 +1,83 @@
+_bs()
+{
+ local cur prev words
+ IFS=$'\n'
+ COMPREPLY=()
+ _get_comp_words_by_ref -n : cur prev words
+
+ # Command data:
+ cmds=$'app\nappsession\nauth\nauthenticate\ncomplete\ncp\ncreate\nhelp\nhistory\nimport\nkill\nlaunch\nlist\nmount\nproject\nregister\nsample\nunmount\nunregister\nupload\nversion\nwhoami'
+ cmds_app=$'import launch'
+ cmds_app_import=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-i\n--appid\n-n\n--appname\n-p\n--properties-file\n-e\n--defaults-file\n-a\n--appsession-id\n-r\n--appversion\n-m\n--appsession-path\n-j\n--input-templates\n-f\n--force'
+ cmds_app_launch=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-i\n--appid\n-a\n--agentid\n-b\n--batch-size\n-n\n--appname\n-o\n--option\n-s\n--sample-attributes\n--disable-consistency-checking'
+ cmds_appsession=$'kill'
+ cmds_appsession_kill=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse'
+ cmds_auth=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n--api-server\n--api-version\n--force\n--scopes\n--client-id\n--client-secret'
+ cmds_authenticate=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n--api-server\n--api-version\n--force\n--scopes\n--client-id\n--client-secret'
+ cmds_complete=$'-h\n--help\n--name\n--shell'
+ cmds_cp=$'-h\n--help'
+ cmds_create=$'project'
+ cmds_create_project=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse'
+ cmds_help=$'-h\n--help'
+ cmds_history=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n--json\n--domain'
+ cmds_import=$'app'
+ cmds_import_app=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-i\n--appid\n-n\n--appname\n-p\n--properties-file\n-e\n--defaults-file\n-a\n--appsession-id\n-r\n--appversion\n-m\n--appsession-path\n-j\n--input-templates\n-f\n--force'
+ cmds_kill=$'appsession'
+ cmds_kill_appsession=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse'
+ cmds_launch=$'app'
+ cmds_launch_app=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-i\n--appid\n-a\n--agentid\n-b\n--batch-size\n-n\n--appname\n-o\n--option\n-s\n--sample-attributes\n--disable-consistency-checking'
+ cmds_mount=$'-h\n--help'
+ cmds_project=$'create'
+ cmds_project_create=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse'
+ cmds_register=$'-h\n--help\n-p\n--path\n-d\n--description\n-g\n--group\n--force'
+ cmds_sample=$'upload'
+ cmds_sample_upload=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-p\n--project\n-i\n--sample-id\n-e\n--experiment\n--show-validation-rules'
+ cmds_unmount=$'-h\n--help'
+ cmds_unregister=$'-h\n--help\n-g\n--group'
+ cmds_upload=$'sample'
+ cmds_upload_sample=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-p\n--project\n-i\n--sample-id\n-e\n--experiment\n--show-validation-rules'
+ cmds_version=$'-h\n--help'
+ cmds_list=$'samples\nprojects\nappsessions\nappresults\napps'
+ cmds_list_samples=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-f\n--format\n-C\n--column\n--max-width\n--noindent\n--quote\n--project-name'
+ cmds_list_projects=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-f\n--format\n-C\n--column\n--max-width\n--noindent\n--quote\n--project-name'
+ cmds_list_appsessions=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-f\n--format\n-C\n--column\n--max-width\n--noindent\n--quote\n--project-name'
+ cmds_list_appresults=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-f\n--format\n-C\n--column\n--max-width\n--noindent\n--quote\n--project-name'
+ cmds_list_apps=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-f\n--format\n-C\n--column\n--max-width\n--noindent\n--quote\n--project-name'
+ cmds_whoami=$'-v\n--verbose\n--log-file\n-q\n--quiet\n-h\n--help\n--debug\n-V\n--version\n--dry-run\n-c\n--config\n--terse\n-f\n--format\n-C\n--column\n--max-width\n--noindent\n--quote'
+
+ cmd=""
+ words[0]=""
+ completed="${cmds}"
+ for var in "${words[@]:1}"
+ do
+ if [[ ${var} == -* ]] ; then
+ break
+ fi
+ if [ -z "${cmd}" ] ; then
+ proposed="${var}"
+ else
+ proposed="${cmd}_${var}"
+ fi
+ local i="cmds_${proposed}"
+ local comp="${!i}"
+ if [ -z "${comp}" ] ; then
+ break
+ fi
+ if [[ ${comp} == -* ]] ; then
+ if [[ ${cur} != -* ]] ; then
+ completed=""
+ break
+ fi
+ fi
+ cmd="${proposed}"
+ completed="${comp}"
+ done
+
+ if [ -z "${completed}" ] ; then
+ COMPREPLY=( $( compgen -f -- "$cur" ) $( compgen -d -- "$cur" ) )
+ else
+ COMPREPLY=( $(compgen -W "${completed}" -- ${cur}) )
+ fi
+ return 0
+}
+complete -o filenames -F _bs bs
diff --git a/test/dotbasespace/default-plugins.json b/test/dotbasespace/default-plugins.json
new file mode 100644
index 0000000..a9fcbee
--- /dev/null
+++ b/test/dotbasespace/default-plugins.json
@@ -0,0 +1 @@
+{"third party": {}}
\ No newline at end of file
diff --git a/test/dotbasespace/default.cfg b/test/dotbasespace/default.cfg
new file mode 100644
index 0000000..b770282
--- /dev/null
+++ b/test/dotbasespace/default.cfg
@@ -0,0 +1,3 @@
+[DEFAULT]
+apiServer = https://api.cloud-hoth.illumina.com/
+accessToken = __ACCESS_TOKEN__
diff --git a/test/dotbasespace/unit_tests-plugins.json b/test/dotbasespace/unit_tests-plugins.json
new file mode 100644
index 0000000..a9fcbee
--- /dev/null
+++ b/test/dotbasespace/unit_tests-plugins.json
@@ -0,0 +1 @@
+{"third party": {}}
\ No newline at end of file
diff --git a/test/dotbasespace/unit_tests.cfg b/test/dotbasespace/unit_tests.cfg
new file mode 100644
index 0000000..b770282
--- /dev/null
+++ b/test/dotbasespace/unit_tests.cfg
@@ -0,0 +1,3 @@
+[DEFAULT]
+apiServer = https://api.cloud-hoth.illumina.com/
+accessToken = __ACCESS_TOKEN__
diff --git a/test/launch_helpers_tests.py b/test/launch_helpers_tests.py
index f0c47b0..2e92c5e 100644
--- a/test/launch_helpers_tests.py
+++ b/test/launch_helpers_tests.py
@@ -36,10 +36,10 @@
'sample-id',
]
app_defaults = {
- 'AnnotationSource': u'RefSeq',
- 'genome-id': u'Human',
- 'GQX-id': u'30',
- 'StrandBias-id': u'10',
+ 'AnnotationSource': 'RefSeq',
+ 'genome-id': 'Human',
+ 'GQX-id': '30',
+ 'StrandBias-id': '10',
'FlagPCRDuplicates-id': []
}
diff --git a/test/test_models.py b/test/test_models.py
index aa79ff9..c323fdd 100644
--- a/test/test_models.py
+++ b/test/test_models.py
@@ -7,79 +7,82 @@
from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI
from BaseSpacePy.model.QueryParameters import QueryParameters as qp
+import six
+
+
class TestSDK(object):
'''
Compares objects from BaseSpace REST API to SDK objects, including pickled objects
'''
- def __init__(self):
-
+ def __init__(self):
+
self.qp = {}
self.rest_method = 'GET'
self.postData = None
self.headerParams=None
self.list_request = False
-
+
# TODO change to unit_tests, but need to add projects/run to account?
- self.myAPI = BaseSpaceAPI(profile="ps_native_hoth")
- self.api = APIClient(self.myAPI.getAccessToken(), self.myAPI.apiServer)
+ self.myAPI = BaseSpaceAPI(profile="ps_native_hoth")
+ self.api = APIClient(self.myAPI.getAccessToken(), self.myAPI.apiServer)
def compare_dict_to_obj(self, rest_dict, p_obj):
- """
+ """
Compare a dictionary from a REST API response and a SDK object for identity.
"""
- for r_key, r_val in rest_dict.iteritems():
+ for r_key, r_val in six.iteritems(rest_dict):
# confirm that the key from REST api exists in stored object
try:
- p_val = getattr(p_obj, r_key)
+ p_val = getattr(p_obj, r_key)
except AttributeError:
- print "REST API attribute '" + r_key + "' doesn't exist in object"
+ print("REST API attribute '" + r_key + "' doesn't exist in object")
else:
- self.classify_rest_item(r_val, p_val, r_key)
+ self.classify_rest_item(r_val, p_val, r_key)
def compare_list_to_obj(self, rest_list, p_obj, r_key):
- """
+ """
Compare a list from a REST API response and an SDK object for identity.
- """
+ """
if type(p_obj) != list:
- print "Attribute '" + r_key + "' is a list in the REST API but not in the object"
+ print("Attribute '" + r_key + "' is a list in the REST API but not in the object")
elif len(p_obj) != len(rest_list):
- print "Attribute '" + r_key + "' has different list length between REST API and object"
+ print("Attribute '" + r_key + "' has different list length between REST API and object")
else:
for r_val, p_val in map(None, rest_list, p_obj):
self.classify_rest_item(r_val, p_val, r_key)
-
+
def compare_builtin_to_obj(self, rest_val, p_obj, r_key):
- """
+ """
Compare a built-in type from a REST API response and an SDK object for identity.
- """
+ """
# convert unicode to ascii for comparisons
- if isinstance(rest_val, unicode):
+ if isinstance(rest_val, six.text_type):
rest_val = rest_val.encode('ascii','ignore')
# don't compare values for datetimes
if r_key in ['DateCreated', 'DateModified', 'DateUploadCompleted', 'DateUploadStarted']:
pass
- elif rest_val != p_obj:
- print "REST API attribute '" + r_key + "' has value '" + str(rest_val) + "' doesn't match object value '" + str(p_obj) + "'"
+ elif rest_val != p_obj:
+ print("REST API attribute '" + r_key + "' has value '" + str(rest_val) + "' doesn't match object value '" + str(p_obj) + "'")
def classify_rest_item(self, r_val, p_val, r_key):
"""
Determine the input REST item's type and call method to compare to input object
- """
- if type(r_val) in [ int, str, bool, float, unicode]:
+ """
+ if type(r_val) in [ int, str, bool, float, six.text_type]:
self.compare_builtin_to_obj(r_val, p_val, r_key)
- elif type(r_val) == dict:
+ elif type(r_val) == dict:
self.compare_dict_to_obj(r_val, p_val)
- elif type(r_val) == list:
+ elif type(r_val) == list:
self.compare_list_to_obj(r_val, p_val, r_key)
else:
- print "REST API attribute'" + r_key + "' has an unrecognized attribute type"
-
+ print("REST API attribute'" + r_key + "' has an unrecognized attribute type")
+
def test_rest_vs_sdk(self):
"""
Compares REST API response and python SDK object for identify, for an API method
- """
+ """
sdk_obj = self.call_sdk()
- rest_obj = self.call_rest_api()
+ rest_obj = self.call_rest_api()
# TODO passing Response here, SDK doesn't currently capture other items at this level (e.g. Notifications)
if self.list_request:
self.compare_list_to_obj(rest_obj['Response']['Items'], sdk_obj, "BASE")
@@ -96,18 +99,18 @@ def create_pickle_from_sdk(self, pickle_path):
"""
Stores a pickled object in the provided path (include file name) for the object returned for this SDK method
"""
- sdk_obj = self.call_sdk()
+ sdk_obj = self.call_sdk()
with open(pickle_path, 'w') as f:
Pickle.dump(sdk_obj, f)
-
+
def get_pickle(self, pickle_path):
"""
Retrieves a pickled object from the provided path (include file name), for this API test
- """
+ """
with open(pickle_path, 'r') as f:
sdk_obj = Pickle.load(f)
- return sdk_obj
-
+ return sdk_obj
+
def test_rest_vs_pickle(self, pickle_path):
"""
Compares REST API response and a stored object for identify, for an API method
@@ -118,46 +121,46 @@ def test_rest_vs_pickle(self, pickle_path):
class GetAppSessionById(TestSDK):
- def __init__(self, bsid):
+ def __init__(self, bsid):
super(GetAppSessionById, self).__init__()
self.rest_path = '/appsessions/' + bsid
-
+
self.appsession_id = bsid
def call_sdk(self):
return self.myAPI.getAppSessionById(self.appsession_id)
class GetAppSessionPropertiesById(TestSDK):
-
+
def __init__(self, ssn_id, query_p={}):
super(GetAppSessionPropertiesById, self).__init__()
self.rest_path = '/appsessions/' + ssn_id + '/properties'
-
+
self.appsession_id = ssn_id
self.qp = query_p
def call_sdk(self):
- return self.myAPI.getAppSessionPropertiesById(self.appsession_id, qp(self.qp))
+ return self.myAPI.getAppSessionPropertiesById(self.appsession_id, qp(self.qp))
class GetAppSessionPropertyByName(TestSDK):
def __init__(self, ssn_id, prop_name, query_p={}):
super(GetAppSessionPropertyByName, self).__init__()
self.rest_path = '/appsessions/' + ssn_id + '/properties/' + prop_name + '/items'
-
+
self.appsession_id = ssn_id
self.property_name = prop_name
self.qp = query_p
def call_sdk(self):
- return self.myAPI.getAppSessionPropertyByName(self.appsession_id, qp(self.qp), self.property_name)
+ return self.myAPI.getAppSessionPropertyByName(self.appsession_id, qp(self.qp), self.property_name)
class GetRunById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetRunById, self).__init__()
self.rest_path = '/runs/' + bsid
-
+
self.run_id = bsid
self.qp = query_p
@@ -166,10 +169,10 @@ def call_sdk(self):
class GetRunPropertiesById(TestSDK):
- def __init__(self, ssn_id, query_p={}):
+ def __init__(self, ssn_id, query_p={}):
super(GetRunPropertiesById, self).__init__()
self.rest_path = '/runs/' + ssn_id + '/properties'
-
+
self.run_id = ssn_id
self.qp = query_p
@@ -178,10 +181,10 @@ def call_sdk(self):
class GetProjectById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetProjectById, self).__init__()
self.rest_path = '/projects/' + bsid
-
+
self.project_id = bsid
self.qp = query_p
@@ -190,10 +193,10 @@ def call_sdk(self):
class GetProjectPropertiesById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetProjectPropertiesById, self).__init__()
self.rest_path = '/projects/' + bsid + '/properties'
-
+
self.project_id = bsid
self.qp = query_p
@@ -202,10 +205,10 @@ def call_sdk(self):
class GetSampleById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetSampleById, self).__init__()
self.rest_path = '/samples/' + bsid
-
+
self.sample_id = bsid
self.qp = query_p
@@ -214,10 +217,10 @@ def call_sdk(self):
class GetSamplePropertiesById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetSamplePropertiesById, self).__init__()
self.rest_path = '/samples/' + bsid + '/properties'
-
+
self.sample_id = bsid
self.qp = query_p
@@ -226,10 +229,10 @@ def call_sdk(self):
class GetAppResultById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetAppResultById, self).__init__()
self.rest_path = '/appresults/' + bsid
-
+
self.appresult_id = bsid
self.qp = query_p
@@ -238,10 +241,10 @@ def call_sdk(self):
class GetAppResultPropertiesById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetAppResultPropertiesById, self).__init__()
self.rest_path = '/appresults/' + bsid + '/properties'
-
+
self.appresult_id = bsid
self.qp = query_p
@@ -250,10 +253,10 @@ def call_sdk(self):
class GetFileById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetFileById, self).__init__()
self.rest_path = '/files/' + bsid
-
+
self.file_id = bsid
self.qp = query_p
@@ -262,10 +265,10 @@ def call_sdk(self):
class GetFilePropertiesById(TestSDK):
- def __init__(self, bsid, query_p={}):
+ def __init__(self, bsid, query_p={}):
super(GetFilePropertiesById, self).__init__()
self.rest_path = '/files/' + bsid + '/properties'
-
+
self.file_id = bsid
self.qp = query_p
@@ -273,26 +276,26 @@ def call_sdk(self):
return self.myAPI.getFilePropertiesById(self.file_id, qp(self.qp))
class FilterVariantSet(TestSDK):
-
- def __init__(self, bsid, chrom, start_pos, end_pos, format ,query_p=None):
+
+ def __init__(self, bsid, chrom, start_pos, end_pos, format ,query_p=None):
super(FilterVariantSet, self).__init__()
- self.rest_path = '/variantset/' + bsid + '/variants/' + chrom
+ self.rest_path = '/variantset/' + bsid + '/variants/' + chrom
if query_p is None:
query_p = {}
-
+
self.file_id = bsid
self.chrom = chrom
self.start_pos = start_pos
self.end_pos = end_pos
- self.format = format
+ self.format = format
self.qp = copy.deepcopy(query_p)
self.qp['StartPos'] = start_pos
self.qp['EndPos'] = end_pos
self.qp['Format'] = format
self.list_request = True
-
+
def call_sdk(self):
- return self.myAPI.filterVariantSet(self.file_id, self.chrom, self.start_pos, self.end_pos, self.format, qp(self.qp))
+ return self.myAPI.filterVariantSet(self.file_id, self.chrom, self.start_pos, self.end_pos, self.format, qp(self.qp))
class TestSuite(object):
@@ -307,73 +310,73 @@ def add_tests(self):
cfg = self.cfg
try:
self.tests.append((FilterVariantSet(cfg['vcf_id'], cfg['vcf_chr'], cfg['vcf_start'], cfg['vcf_end'], cfg['vcf_format'], cfg['query_p']), "with query parameter"))
- except AttributeError:
- print "Skipping test FilterVariantSet -- missing input parameter"
+ except AttributeError:
+ print("Skipping test FilterVariantSet -- missing input parameter")
try:
self.tests.append((GetAppSessionById(cfg['ssn_id']), "test"))
except AttributeError:
- print "Skipping test GetAppSessionById -- missing input parameter"
+ print("Skipping test GetAppSessionById -- missing input parameter")
try:
self.tests.append((GetAppSessionPropertiesById(cfg['ssn_id'], cfg['query_p']), "with query parameter"))
except AttributeError:
- print "Skipping test GetAppSessionPropertiesById -- missing input parameter"
- try:
- for key, value in cfg['multivalue_property_names'].iteritems():
+ print("Skipping test GetAppSessionPropertiesById -- missing input parameter")
+ try:
+ for key, value in six.iteritems(cfg['multivalue_property_names']):
self.tests.append((GetAppSessionPropertyByName(cfg['ssn_id'], value, cfg['query_p']), key))
except AttributeError:
- print "Skipping test GetAppSessionPropertiesByName -- missing input parameter"
+ print("Skipping test GetAppSessionPropertiesByName -- missing input parameter")
try:
self.tests.append((GetRunById(cfg['run_id'], cfg['query_p']), "with query parameter"))
except AttributeError:
- print "Skipping test GetRunById -- missing input parameter"
+ print("Skipping test GetRunById -- missing input parameter")
try:
self.tests.append((GetRunPropertiesById(cfg['run_id'], cfg['query_p']), "with query parameter"))
except AttributeError:
- print "Skipping test GetRunPropertiesById -- missing input parameter"
+ print("Skipping test GetRunPropertiesById -- missing input parameter")
try:
self.tests.append((GetProjectById(cfg['project_id'], cfg['query_p']), "with query parameter"))
except AttributeError:
- print "Skipping test GetProjectById -- missing input parameter"
+ print("Skipping test GetProjectById -- missing input parameter")
try:
self.tests.append((GetProjectPropertiesById(cfg['project_id'], cfg['query_p']), "with query parameter"))
- except AttributeError:
- print "Skipping test GetProjectPropertiesById -- missing input parameter"
+ except AttributeError:
+ print("Skipping test GetProjectPropertiesById -- missing input parameter")
try:
self.tests.append((GetSampleById(cfg['sample_id'], cfg['query_p']), "with query parameter"))
except AttributeError:
- print "Skipping test GetSampleById -- missing input parameter"
+ print("Skipping test GetSampleById -- missing input parameter")
try:
self.tests.append((GetSamplePropertiesById(cfg['sample_id'], cfg['query_p']), "with query parameter"))
- except AttributeError:
- print "Skipping test GetSamplePropertiesById -- missing input parameter"
+ except AttributeError:
+ print("Skipping test GetSamplePropertiesById -- missing input parameter")
try:
self.tests.append((GetAppResultById(cfg['appresult_id'], cfg['query_p']), "with query parameter"))
except AttributeError:
- print "Skipping test GetAppResultById -- missing input parameter"
+ print("Skipping test GetAppResultById -- missing input parameter")
try:
self.tests.append((GetAppResultPropertiesById(cfg['appresult_id'], cfg['query_p']), "with query parameter"))
- except AttributeError:
- print "Skipping test GetAppResultPropertiesById -- missing input parameter"
+ except AttributeError:
+ print("Skipping test GetAppResultPropertiesById -- missing input parameter")
try:
self.tests.append((GetFileById(cfg['file_id'], cfg['query_p']), "with query parameter"))
except AttributeError:
- print "Skipping test GetFileById -- missing input parameter"
+ print("Skipping test GetFileById -- missing input parameter")
try:
self.tests.append((GetFilePropertiesById(cfg['file_id'], cfg['query_p']), "with query parameter"))
- except AttributeError:
- print "Skipping test GetFilePropertiesById -- missing input parameter"
+ except AttributeError:
+ print("Skipping test GetFilePropertiesById -- missing input parameter")
+
-
def test_rest_vs_sdk(self):
for test in self.tests:
- print "\nTesting REST vs SDK for " + test[0].__class__.__name__ + "' with comment '" + test[1] + "'"
+ print("\nTesting REST vs SDK for " + test[0].__class__.__name__ + "' with comment '" + test[1] + "'")
try:
test[0].test_rest_vs_sdk()
except Exception as e:
- print "Exception: " + str(e)
+ print("Exception: " + str(e))
if __name__ == '__main__':
-
+
# BaseSaceAPI profile (in TestSDK.__init__) must have permission to read the items in cfg
cfg = {}
cfg['ssn_id'] = '1300310'
@@ -393,8 +396,8 @@ def test_rest_vs_sdk(self):
'runs': 'Input.run-id2',
#'map': None,
'maps': 'Input.app-result-id2.attributes',
- }
- cfg['run_id'] = '523524'
+ }
+ cfg['run_id'] = '523524'
cfg['project_id'] = '2'
cfg['sample_id'] = '1021'
cfg['appresult_id'] = '21'
@@ -404,11 +407,10 @@ def test_rest_vs_sdk(self):
cfg['vcf_start'] = '1'
cfg['vcf_end'] = '4000'
cfg['vcf_format'] = 'txt' # or 'vcf'
-
-
- # Run all tests in the test suite for 'test_app1'
+
+
+ # Run all tests in the test suite for 'test_app1'
#suite = TestSuite(app_data.test_app1)
suite = TestSuite(cfg)
suite.add_tests()
- suite.test_rest_vs_sdk()
-
+ suite.test_rest_vs_sdk()
diff --git a/test/unit_tests.py b/test/unit_tests.py
index d71e95d..0bfd058 100644
--- a/test/unit_tests.py
+++ b/test/unit_tests.py
@@ -3,12 +3,12 @@
import sys
from tempfile import mkdtemp
import shutil
-from urlparse import urlparse, urljoin
import multiprocessing
import hashlib
import webbrowser
import time
import json
+import base64
from BaseSpacePy.api.BaseSpaceAPI import BaseSpaceAPI, deviceURL
from BaseSpacePy.api.BaseAPI import BaseAPI
from BaseSpacePy.api.APIClient import APIClient
@@ -19,27 +19,31 @@
+from six.moves.urllib.parse import urlparse, urljoin
+
+
# Dependencies:
# ============
-# 1. Create a profile named 'unit_tests' in ~/.basespacepy.cfg that has the credentials for an app on https://portal-hoth.illumina.com;
-# (there should also be a 'DEFALT' profile in the config file)
+# 1. Create a config file in ~/.basespace/unit_tests.cfg that has the credentials for an app on https://portal-hoth.illumina.com;
+# you can do this with: bs -c unit_tests authenticate --api-server https://api.cloud-hoth.illumina.com/
# 2. Import the following data from Public Dataset 'MiSeq B. cereus demo data' on cloud-hoth.illumina.com:
# 2.a. Project name 'BaseSpaceDemo' (Id 596596), and
# 2.b. Run name 'BacillusCereus' (Id 555555)
-# 3. Download the following fastq file from BaseSpaceDemo's samples section:
+# 3. Download the following fastq file from BaseSpaceDemo's samples section:
# < https://cloud-hoth.illumina.com/sample/855866/files/tree/BC-12_S12_L001_R2_001.fastq.gz?id=9896135 >
# and place it into the 'data' directory of this repository. It's 56MB in size.
#
# Note that large file upload download tests may take minutes each to complete, and oauth tests will open web browsers.
-tconst = {
+tconst = {
# for download tests
'file_id_small': '9896072', # 2.2 KB, public data B. cereus Project, data/intentisties/basecalls/Alignment/DemultiplexSummaryF1L1.9.txt
- 'file_id_large': '9896135', # 55.31 MB public data B. cereus Project, data/intensities/basecalls/BC-12_S12_L001_R2_001.fastq.gz
+ 'file_id_large': '9896135', # 55.31 MB public data B. cereus Project, data/intensities/basecalls/BC-12_S12_L001_R2_001.fastq.gz
'file_small_md5': '4c3328bcf26ffb54da4de7b3c8879f94', # for file id 9896072
- 'file_large_md5': '9267236a2d870da1d4cb73868bb51b35', # for file id 9896135
+ 'file_large_md5': '9267236a2d870da1d4cb73868bb51b35', # for file id 9896135
# for upload tests
'file_small_upload': 'data/test.small.upload.txt',
+ 'file_small_upload_contents': open('data/test.small.upload.txt').read(),
'file_large_upload': 'data/BC-12_S12_L001_R2_001.fastq.gz',
'file_small_upload_size': 11,
'file_large_upload_size': 57995799,
@@ -61,7 +65,7 @@
'PF_count': '446158',
'appresult_id': '1213212',
'appresult_referenced_sample_id': '855855',
- #'appsession_id': '1305304', TEMP
+ #'appsession_id': '1305304', TEMP
# for coverage and variant apis
'bam_file_id': '9895890',
'bam_cov_chr_name': 'chr',
@@ -70,25 +74,25 @@
'vcf_file_id': '9895892',
'vcf_chr_name': 'chr',
'vcf_start_coord': '1',
- 'vcf_end_coord': '200000',
+ 'vcf_end_coord': '200000',
}
class TestFileDownloadMethods(TestCase):
'''
Tests methods of File objects
'''
- def setUp(self):
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
self.file = self.api.getFileById(tconst['file_id_small'])
- self.temp_dir = mkdtemp()
-
+ self.temp_dir = mkdtemp()
+
def tearDown(self):
- shutil.rmtree(self.temp_dir)
-
+ shutil.rmtree(self.temp_dir)
+
def testDownloadFile(self):
new_file = self.file.downloadFile(
self.api,
- localDir = self.temp_dir,
+ localDir = self.temp_dir,
)
file_path = os.path.join(self.temp_dir, new_file.Name)
self.assertTrue(os.path.isfile(file_path))
@@ -97,12 +101,12 @@ def testDownloadFile(self):
with open(file_path, "r+b") as fp:
self.assertEqual(Utils.md5_for_file(fp), tconst['file_small_md5'])
os.remove(file_path)
-
+
def testDownloadFileWithBsDirectoryArg(self):
new_file = self.file.downloadFile(
self.api,
localDir = self.temp_dir,
- createBsDir = True,
+ createBsDir = True,
)
file_path = os.path.join(self.temp_dir, new_file.Path)
self.assertTrue(os.path.isfile(file_path))
@@ -111,68 +115,68 @@ def testDownloadFileWithBsDirectoryArg(self):
with open(file_path, "r+b") as fp:
self.assertEqual(Utils.md5_for_file(fp), tconst['file_small_md5'])
os.remove(file_path)
-
+
def testDownloadFileWithByteRangeArg(self):
new_file = self.file.downloadFile(
self.api,
localDir = self.temp_dir,
- byteRange = [1000,2000]
+ byteRange = [1000,2000]
)
file_path = os.path.join(self.temp_dir, new_file.Name)
self.assertTrue(os.path.isfile(file_path))
# confirm file size is correct
self.assertEqual(1001, os.stat(file_path).st_size)
- os.remove(file_path)
+ os.remove(file_path)
class TestAPIFileUploadMethods_SmallFiles(TestCase):
'''
Tests single and multi-part upload methods
'''
@classmethod
- def setUpClass(cls):
+ def setUpClass(cls):
'''
For all upload unit tests (not per test):
Create a new 'unit test' project, or get it if exists, to upload to data to.
Then create a new app result in this project, getting a new app session id
- '''
- cls.api = BaseSpaceAPI(profile='unit_tests')
- cls.proj = cls.api.createProject(tconst['create_project_name'])
+ '''
+ cls.api = BaseSpaceAPI(profile='unit_tests')
+ cls.proj = cls.api.createProject(tconst['create_project_name'])
cls.ar = cls.proj.createAppResult(cls.api, "test upload", "test upload", appSessionId="")
-
- def test__singlepartFileUpload__(self):
+
+ def test__singlepartFileUpload__(self):
testDir = "testSinglePartSmallFileUploadDirectory"
fileName = os.path.basename(tconst['file_small_upload'])
myFile = self.api.__singlepartFileUpload__(
resourceType = 'appresults',
resourceId = self.ar.Id,
- localPath=tconst['file_small_upload'],
- fileName=fileName,
- directory=testDir,
- contentType=tconst['file_small_upload_content_type'])
+ localPath=tconst['file_small_upload'],
+ fileName=fileName,
+ directory=testDir,
+ contentType=tconst['file_small_upload_content_type'])
self.assertEqual(myFile.Path, os.path.join(testDir, fileName))
self.assertEqual(myFile.Size, tconst['file_small_upload_size'])
self.assertEqual(myFile.UploadStatus, 'complete')
# test fresh File object
newFile = self.api.getFileById(myFile.Id)
- self.assertEqual(newFile.Path, os.path.join(testDir, fileName))
+ self.assertEqual(newFile.Path, os.path.join(testDir, fileName))
self.assertEqual(newFile.Size, tconst['file_small_upload_size'])
- self.assertEqual(newFile.UploadStatus, 'complete')
+ self.assertEqual(newFile.UploadStatus, 'complete')
def testAppResultFileUpload_SmallUpload(self):
testDir = "testSmallUploadDirectory"
fileName = os.path.basename(tconst['file_small_upload'])
myFile = self.api.appResultFileUpload(
- Id=self.ar.Id,
- localPath=tconst['file_small_upload'],
- fileName=fileName,
- directory=testDir,
- contentType=tconst['file_small_upload_content_type'])
+ Id=self.ar.Id,
+ localPath=tconst['file_small_upload'],
+ fileName=fileName,
+ directory=testDir,
+ contentType=tconst['file_small_upload_content_type'])
self.assertEqual(myFile.Path, os.path.join(testDir, fileName))
self.assertEqual(myFile.Size, tconst['file_small_upload_size'])
self.assertEqual(myFile.UploadStatus, 'complete')
# test fresh File object
newFile = self.api.getFileById(myFile.Id)
- self.assertEqual(newFile.Path, os.path.join(testDir, fileName))
+ self.assertEqual(newFile.Path, os.path.join(testDir, fileName))
self.assertEqual(newFile.Size, tconst['file_small_upload_size'])
self.assertEqual(newFile.UploadStatus, 'complete')
@@ -181,46 +185,46 @@ def test__initiateMultipartFileUpload__(self):
file = self.api.__initiateMultipartFileUpload__(
resourceType = 'appresults',
resourceId = self.ar.Id,
- fileName = os.path.basename(tconst['file_small_upload']),
+ fileName = os.path.basename(tconst['file_small_upload']),
directory = testDir,
contentType=tconst['file_small_upload_content_type'])
- self.assertEqual(file.Name, os.path.basename(tconst['file_small_upload']))
-
+ self.assertEqual(file.Name, os.path.basename(tconst['file_small_upload']))
+
def test__uploadMultipartUnit__(self):
testDir = "test__uploadMultipartUnit__"
file = self.api.__initiateMultipartFileUpload__(
resourceType = 'appresults',
resourceId = self.ar.Id,
- fileName = os.path.basename(tconst['file_small_upload']),
+ fileName = os.path.basename(tconst['file_small_upload']),
directory = testDir,
contentType=tconst['file_small_upload_content_type'])
with open(tconst['file_small_upload']) as fp:
out = fp.read()
- md5 = hashlib.md5(out).digest().encode('base64')
+ md5 = base64.b64encode(hashlib.md5(out.encode('utf-8')).digest())
response = self.api.__uploadMultipartUnit__(
Id = file.Id,
partNumber = 1,
md5 = md5,
- data = tconst['file_small_upload'])
+ data = tconst['file_small_upload_contents'])
self.assertNotEqual(response, None, 'Upload part failure will return None')
self.assertTrue('ETag' in response['Response'], 'Upload part success will contain a Response dict with an ETag element')
-
+
def test__finalizeMultipartFileUpload__(self):
testDir = "test__finalizeMultipartFileUpload__"
file = self.api.__initiateMultipartFileUpload__(
resourceType = 'appresults',
resourceId = self.ar.Id,
- fileName = os.path.basename(tconst['file_small_upload']),
+ fileName = os.path.basename(tconst['file_small_upload']),
directory = testDir,
contentType=tconst['file_small_upload_content_type'])
with open(tconst['file_small_upload']) as fp:
out = fp.read()
- md5 = hashlib.md5(out).digest().encode('base64')
+ md5 = base64.b64encode(hashlib.md5(out.encode('utf-8')).digest())
response = self.api.__uploadMultipartUnit__(
Id = file.Id,
partNumber = 1,
md5 = md5,
- data = tconst['file_small_upload'])
+ data = tconst['file_small_upload_contents'])
final_file = self.api.__finalizeMultipartFileUpload__(file.Id)
self.assertEqual(final_file.UploadStatus, 'complete')
@@ -229,11 +233,11 @@ def testMultiPartFileUpload_SmallPartSizeException(self):
myFile = self.api.multipartFileUpload(
resourceType = 'appresults',
resourceId = self.ar.Id,
- localPath=tconst['file_large_upload'],
- fileName=os.path.basename(tconst['file_large_upload']),
- directory="",
- contentType=tconst['file_large_upload_content_type'],
- partSize=5, # MB, chunk size
+ localPath=tconst['file_large_upload'],
+ fileName=os.path.basename(tconst['file_large_upload']),
+ directory="",
+ contentType=tconst['file_large_upload_content_type'],
+ partSize=5, # MB, chunk size
)
def testMultiPartFileUpload_LargePartSizeException(self):
@@ -241,21 +245,21 @@ def testMultiPartFileUpload_LargePartSizeException(self):
myFile = self.api.multipartFileUpload(
resourceType = 'appresults',
resourceId = self.ar.Id,
- localPath=tconst['file_large_upload'],
- fileName=os.path.basename(tconst['file_large_upload']),
- directory="",
- contentType=tconst['file_large_upload_content_type'],
- partSize=26, # MB, chunk size
+ localPath=tconst['file_large_upload'],
+ fileName=os.path.basename(tconst['file_large_upload']),
+ directory="",
+ contentType=tconst['file_large_upload_content_type'],
+ partSize=26, # MB, chunk size
)
- def testIntegration_SmallFileUploadThenDownload(self):
+ def testIntegration_SmallFileUploadThenDownload(self):
upFile = self.api.appResultFileUpload(
- Id=self.ar.Id,
- localPath=tconst['file_small_upload'],
- fileName=os.path.basename(tconst['file_small_upload']),
- directory="test_upload_download_dir",
- contentType=tconst['file_small_upload_content_type'])
- tempDir = mkdtemp()
+ Id=self.ar.Id,
+ localPath=tconst['file_small_upload'],
+ fileName=os.path.basename(tconst['file_small_upload']),
+ directory="test_upload_download_dir",
+ contentType=tconst['file_small_upload_content_type'])
+ tempDir = mkdtemp()
downFile = self.api.fileDownload(upFile.Id, tempDir, createBsDir=True)
downPath = os.path.join(tempDir, upFile.Path)
self.assertTrue(os.path.isfile(downPath), "Failed to find path %s" % downPath)
@@ -263,7 +267,7 @@ def testIntegration_SmallFileUploadThenDownload(self):
self.assertEqual(os.path.getsize(tconst['file_small_upload']), os.path.getsize(downPath))
with open(downPath, "r+b") as fp:
self.assertEqual(Utils.md5_for_file(fp), tconst['file_small_upload_md5'])
- os.remove(downPath)
+ os.remove(downPath)
class TestMultipartFileTransferMethods(TestCase):
@@ -279,65 +283,64 @@ class TestAPIFileUploadMethods_LargeFiles(TestCase):
Tests multi-part upload methods on large(-ish) files -- may be time consuming
'''
@classmethod
- def setUpClass(cls):
+ def setUpClass(cls):
'''
For all upload unit tests (not per test):
Create a new 'unit test' project, or get it if exists, to upload to data to.
Then create a new app result in this project, getting a new app session id
- '''
+ '''
cls.api = BaseSpaceAPI(profile='unit_tests')
cls.proj = cls.api.createProject(tconst['create_project_name'])
cls.ar = cls.proj.createAppResult(cls.api, "test upload", "test upload", appSessionId="")
-
+
# @skip('large upload')
def testAppResultFileUpload_LargeUpload(self):
testDir = "testLargeUploadDirectory"
- fileName = os.path.basename(tconst['file_large_upload'])
+ fileName = os.path.basename(tconst['file_large_upload'])
myFile = self.api.appResultFileUpload(
- Id=self.ar.Id,
- localPath=tconst['file_large_upload'],
- fileName=fileName,
- directory=testDir,
+ Id=self.ar.Id,
+ localPath=tconst['file_large_upload'],
+ fileName=fileName,
+ directory=testDir,
contentType=tconst['file_small_upload_content_type'])
self.assertEqual(myFile.Path, os.path.join(testDir, fileName))
self.assertEqual(myFile.Size, tconst['file_large_upload_size'])
self.assertEqual(myFile.UploadStatus, 'complete')
# test fresh File object
newFile = self.api.getFileById(myFile.Id)
- self.assertEqual(newFile.Path, os.path.join(testDir, fileName))
+ self.assertEqual(newFile.Path, os.path.join(testDir, fileName))
self.assertEqual(newFile.Size, tconst['file_large_upload_size'])
self.assertEqual(newFile.UploadStatus, 'complete')
-
+
# @skip('large upload')
def testMultiPartFileUpload(self):
testDir = "testMultipartUploadDir"
- fileName = os.path.basename(tconst['file_large_upload'])
+ fileName = os.path.basename(tconst['file_large_upload'])
myFile = self.api.multipartFileUpload(
resourceType = 'appresults',
resourceId = self.ar.Id,
- localPath=tconst['file_large_upload'],
- fileName=fileName,
- directory=testDir,
+ localPath=tconst['file_large_upload'],
+ fileName=fileName,
+ directory=testDir,
contentType=tconst['file_large_upload_content_type'],
- tempDir=None,
processCount = 4,
- partSize= 10, # MB, chunk size
+ partSize= 10, # MB, chunk size
#tempDir = args.temp_dir
- )
+ )
self.assertEqual(myFile.Size, tconst['file_large_upload_size'])
self.assertEqual(myFile.Name, fileName)
- self.assertEqual(myFile.Path, os.path.join(testDir, fileName))
- self.assertEqual(myFile.UploadStatus, 'complete')
+ self.assertEqual(myFile.Path, os.path.join(testDir, fileName))
+ self.assertEqual(myFile.UploadStatus, 'complete')
# @skip('large upload and download')
- def testIntegration_LargeFileUploadThenDownload(self):
+ def testIntegration_LargeFileUploadThenDownload(self):
upFile = self.api.appResultFileUpload(
- Id=self.ar.Id,
- localPath=tconst['file_large_upload'],
- fileName=os.path.basename(tconst['file_large_upload']),
- directory="test_upload_download_dir",
- contentType=tconst['file_large_upload_content_type'])
- tempDir = mkdtemp()
+ Id=self.ar.Id,
+ localPath=tconst['file_large_upload'],
+ fileName=os.path.basename(tconst['file_large_upload']),
+ directory="test_upload_download_dir",
+ contentType=tconst['file_large_upload_content_type'])
+ tempDir = mkdtemp()
downFile = self.api.fileDownload(upFile.Id, tempDir, createBsDir=True)
downPath = os.path.join(tempDir, upFile.Path)
self.assertTrue(os.path.isfile(downPath), "Failed to find path %s" % downPath)
@@ -345,26 +348,26 @@ def testIntegration_LargeFileUploadThenDownload(self):
self.assertEqual(os.path.getsize(tconst['file_large_upload']), os.path.getsize(downPath))
with open(downPath, "r+b") as fp:
self.assertEqual(Utils.md5_for_file(fp), tconst['file_large_upload_md5'])
- os.remove(downPath)
-
+ os.remove(downPath)
+
class TestAPIFileDownloadMethods_SmallFiles(TestCase):
'''
Tests single and multi-part download methods
'''
- def setUp(self):
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
- self.temp_dir = mkdtemp()
-
+ self.temp_dir = mkdtemp()
+
def tearDown(self):
- shutil.rmtree(self.temp_dir)
+ shutil.rmtree(self.temp_dir)
def test__downloadFile__(self):
file_name = 'testfile.abc'
bs_file = self.api.getFileById(tconst['file_id_small'])
self.api.__downloadFile__(
- tconst['file_id_small'],
+ tconst['file_id_small'],
localDir = self.temp_dir,
- name = file_name,
+ name = file_name,
)
file_path = os.path.join(self.temp_dir, file_name)
self.assertTrue(os.path.isfile(file_path))
@@ -373,43 +376,43 @@ def test__downloadFile__(self):
with open(file_path, "r+b") as fp:
self.assertEqual(Utils.md5_for_file(fp), tconst['file_small_md5'])
os.remove(file_path)
-
+
def test__downloadFile__WithByteRangeArg(self):
- file_name = 'testfile.abc'
+ file_name = 'testfile.abc'
self.api.__downloadFile__(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
name = file_name,
- byteRange = [2000,3000]
+ byteRange = [2000,3000]
)
file_path = os.path.join(self.temp_dir, file_name)
- self.assertTrue(os.path.isfile(file_path))
+ self.assertTrue(os.path.isfile(file_path))
self.assertEqual(3001, os.stat(file_path).st_size) # seek() into file, so size is larger
os.remove(file_path)
def test__downloadFile__WithByteRangeStoredInStandaloneFile(self):
file_name = 'testfile.abc'
self.api.__downloadFile__(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
name = file_name,
byteRange = [2000,3000],
- standaloneRangeFile = True,
+ standaloneRangeFile = True,
)
file_path = os.path.join(self.temp_dir, file_name)
- self.assertTrue(os.path.isfile(file_path))
+ self.assertTrue(os.path.isfile(file_path))
self.assertEqual(1001, os.stat(file_path).st_size) # no seek() into standalone file, so size is only range data
os.remove(file_path)
-
+
def test__downloadFile__WithLockArg(self):
lock = multiprocessing.Lock() # just testing that passing in a lock won't crash anything
file_name = 'testfile.abc'
bs_file = self.api.getFileById(tconst['file_id_small'])
self.api.__downloadFile__(
- tconst['file_id_small'],
+ tconst['file_id_small'],
localDir = self.temp_dir,
name = file_name,
- lock = lock,
+ lock = lock,
)
file_path = os.path.join(self.temp_dir, file_name)
self.assertTrue(os.path.isfile(file_path))
@@ -417,12 +420,12 @@ def test__downloadFile__WithLockArg(self):
self.assertEqual(bs_file.Size, os.stat(file_path).st_size)
with open(file_path, "r+b") as fp:
self.assertEqual(Utils.md5_for_file(fp), tconst['file_small_md5'])
- os.remove(file_path)
-
+ os.remove(file_path)
+
def testFileDownload_SmallFile(self):
new_file = self.api.fileDownload(
- tconst['file_id_small'],
- localDir = self.temp_dir,
+ tconst['file_id_small'],
+ localDir = self.temp_dir,
)
file_path = os.path.join(self.temp_dir, new_file.Name)
self.assertTrue(os.path.isfile(file_path))
@@ -434,9 +437,9 @@ def testFileDownload_SmallFile(self):
def testFileDownload_SmallFileWithBsDirectoryArg(self):
new_file = self.api.fileDownload(
- tconst['file_id_small'],
+ tconst['file_id_small'],
localDir = self.temp_dir,
- createBsDir = True,
+ createBsDir = True,
)
file_path = os.path.join(self.temp_dir, new_file.Path)
self.assertTrue(os.path.isfile(file_path))
@@ -448,43 +451,43 @@ def testFileDownload_SmallFileWithBsDirectoryArg(self):
def testFileDownload_WithByteRangeArg(self):
new_file = self.api.fileDownload(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
- byteRange = [1000,2000]
+ byteRange = [1000,2000]
)
file_path = os.path.join(self.temp_dir, new_file.Name)
self.assertTrue(os.path.isfile(file_path))
# confirm file size is correct
self.assertEqual(1001, os.stat(file_path).st_size)
- os.remove(file_path)
+ os.remove(file_path)
def testFileDownload_LargeByteRangeException(self):
with self.assertRaises(ByteRangeException):
self.api.fileDownload(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
- byteRange = [1,10000001]
- )
+ byteRange = [1,10000001]
+ )
def testFileDownload_MisorderedByteRangeException(self):
with self.assertRaises(ByteRangeException):
self.api.fileDownload(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
- byteRange = [1000, 1]
+ byteRange = [1000, 1]
)
def testFileDownload_PartialByteRangeException(self):
with self.assertRaises(ByteRangeException):
self.api.fileDownload(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
- byteRange = [1000]
+ byteRange = [1000]
)
def testMultipartFileDownload_SmallFile(self):
new_file = self.api.multipartFileDownload(
- tconst['file_id_small'],
+ tconst['file_id_small'],
localDir = self.temp_dir,
processCount = 10,
partSize = 12
@@ -499,7 +502,7 @@ def testMultipartFileDownload_SmallFile(self):
def testMultipartFileDownload_WithBsDirectoryArg(self):
new_file = self.api.multipartFileDownload(
- tconst['file_id_small'],
+ tconst['file_id_small'],
localDir = self.temp_dir,
processCount = 10,
partSize = 12,
@@ -515,13 +518,13 @@ def testMultipartFileDownload_WithBsDirectoryArg(self):
def testMultipartFileDownload_WithTempFileArg(self):
new_file = self.api.multipartFileDownload(
- tconst['file_id_small'],
- localDir = self.temp_dir,
+ tconst['file_id_small'],
+ localDir = self.temp_dir,
tempDir = self.temp_dir
)
file_path = os.path.join(self.temp_dir, new_file.Name)
self.assertTrue(os.path.isfile(file_path))
- # confirm file size and md5 are correct
+ # confirm file size and md5 are correct
self.assertEqual(new_file.Size, os.stat(file_path).st_size)
fp = open(file_path, "r+b")
self.assertEqual(Utils.md5_for_file(fp), tconst['file_small_md5'])
@@ -529,14 +532,14 @@ def testMultipartFileDownload_WithTempFileArg(self):
def testMultipartFileDownload_WithTempFileAndBsDirArgs(self):
new_file = self.api.multipartFileDownload(
- tconst['file_id_small'],
- localDir = self.temp_dir,
+ tconst['file_id_small'],
+ localDir = self.temp_dir,
tempDir = self.temp_dir,
createBsDir = True,
)
file_path = os.path.join(self.temp_dir, new_file.Path)
self.assertTrue(os.path.isfile(file_path))
- # confirm file size and md5 are correct
+ # confirm file size and md5 are correct
self.assertEqual(new_file.Size, os.stat(file_path).st_size)
fp = open(file_path, "r+b")
self.assertEqual(Utils.md5_for_file(fp), tconst['file_small_md5'])
@@ -546,18 +549,18 @@ class TestAPIFileDownloadMethods_LargeFiles(TestCase):
'''
Tests multi-part download methods on large(-ish) files -- may be time consuming
'''
- def setUp(self):
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
- self.temp_dir = mkdtemp()
-
+ self.temp_dir = mkdtemp()
+
def tearDown(self):
- shutil.rmtree(self.temp_dir)
+ shutil.rmtree(self.temp_dir)
# @skip('large download')
def testFileDownload_LargeFile(self):
new_file = self.api.fileDownload(
- tconst['file_id_large'],
- localDir = self.temp_dir,
+ tconst['file_id_large'],
+ localDir = self.temp_dir,
)
file_path = os.path.join(self.temp_dir, new_file.Name)
self.assertTrue(os.path.isfile(file_path))
@@ -570,9 +573,9 @@ def testFileDownload_LargeFile(self):
# @skip('large download')
def testFileDownload_LargeFileWithBsDirectoryArg(self):
new_file = self.api.fileDownload(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
- createBsDir = True,
+ createBsDir = True,
)
file_path = os.path.join(self.temp_dir, new_file.Path)
self.assertTrue(os.path.isfile(file_path))
@@ -585,7 +588,7 @@ def testFileDownload_LargeFileWithBsDirectoryArg(self):
# @skip('large download')
def testMultipartFileDownload_LargeFile(self):
new_file = self.api.multipartFileDownload(
- tconst['file_id_large'],
+ tconst['file_id_large'],
localDir = self.temp_dir,
processCount = 10,
partSize = 12
@@ -602,55 +605,55 @@ class TestAppResultMethods(TestCase):
'''
Tests AppResult object methods
'''
- def setUp(self):
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
self.appResult = self.api.getAppResultById(tconst['appresult_id'])
-
- def testIsInit(self):
+
+ def testIsInit(self):
self.assertEqual(self.appResult.isInit(), True)
-
+
def testIsInitException(self):
- appResult = AppResult.AppResult()
+ appResult = AppResult.AppResult()
with self.assertRaises(ModelNotInitializedException):
- appResult.isInit()
+ appResult.isInit()
def testGetAccessString(self):
self.assertEqual(self.appResult.getAccessStr(), 'write appresult ' + self.appResult.Id)
-
+
def testGetAccessStringWithArg(self):
self.assertEqual(self.appResult.getAccessStr('read'), 'read appresult ' + self.appResult.Id)
-
+
def testGetReferencedSamplesIds(self):
self.assertEqual(self.appResult.getReferencedSamplesIds(), [tconst['appresult_referenced_sample_id']])
-
+
def testGetReferencedSamples(self):
samples = self.appResult.getReferencedSamples(self.api)
self.assertEqual(samples[0].Id, tconst['appresult_referenced_sample_id'])
-
+
def testGetFiles(self):
- files = self.appResult.getFiles(self.api)
+ files = self.appResult.getFiles(self.api)
self.assertTrue(hasattr(files[0], 'Id'))
def testGetFilesWithQp(self):
- files = self.appResult.getFiles(self.api, qp({'Limit':1}))
+ files = self.appResult.getFiles(self.api, qp({'Limit':1}))
self.assertTrue(hasattr(files[0], 'Id'))
self.assertEqual(len(files), 1)
-
+
def testUploadFile(self):
'''
Create a new 'unit test' project, or get it if exists, to upload to data to.
Then create a new appresult in this project, getting a new appsession id
Then...upload a file to the new appresult
'''
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test appresult upload", "test appresult upload", appSessionId="")
testDir = "testSmallUploadAppResultDirectory"
fileName = os.path.basename(tconst['file_small_upload'])
myFile = ar.uploadFile(
- api=self.api,
- localPath=tconst['file_small_upload'],
- fileName=fileName,
- directory=testDir,
+ api=self.api,
+ localPath=tconst['file_small_upload'],
+ fileName=fileName,
+ directory=testDir,
contentType=tconst['file_small_upload_content_type'])
self.assertEqual(myFile.Path, os.path.join(testDir, fileName))
self.assertEqual(myFile.Size, tconst['file_small_upload_size'])
@@ -659,72 +662,72 @@ def testUploadFile(self):
newFile = self.api.getFileById(myFile.Id)
self.assertEqual(newFile.Path, os.path.join(testDir, fileName))
self.assertEqual(newFile.Size, tconst['file_small_upload_size'])
- self.assertEqual(newFile.UploadStatus, 'complete')
+ self.assertEqual(newFile.UploadStatus, 'complete')
class TestAPIAppResultMethods(TestCase):
'''
Tests API object AppResult methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
def testGetAppResultById(self):
appresult = self.api.getAppResultById(tconst['appresult_id'])
self.assertTrue(appresult.Id, 'appresult_id')
-
+
def testGetAppResultByIdWithQp(self):
appresult = self.api.getAppResultById(tconst['appresult_id'], qp({'Limit':1})) # Limit doesn't make sense here
- self.assertTrue(appresult.Id, 'appresult_id')
-
+ self.assertTrue(appresult.Id, 'appresult_id')
+
def testGetAppResultPropertiesById(self):
- props = self.api.getAppResultPropertiesById(tconst['appresult_id'])
+ props = self.api.getAppResultPropertiesById(tconst['appresult_id'])
self.assertTrue(hasattr(props, 'TotalCount'))
-
+
def testGetAppResultPropertiesByIdWithQp(self):
props = self.api.getAppResultPropertiesById(tconst['appresult_id'], qp({'Limit':1}))
- self.assertTrue(hasattr(props, 'TotalCount'))
+ self.assertTrue(hasattr(props, 'TotalCount'))
self.assertEqual(len(props.Items), 1)
def testGetAppResultFilesById(self):
- files = self.api.getAppResultFilesById(tconst['appresult_id'])
+ files = self.api.getAppResultFilesById(tconst['appresult_id'])
self.assertTrue(hasattr(files[0], 'Id'))
-
+
def testGetAppResultFilesByIdWithQp(self):
- files = self.api.getAppResultFilesById(tconst['appresult_id'], qp({'Limit':1}))
+ files = self.api.getAppResultFilesById(tconst['appresult_id'], qp({'Limit':1}))
self.assertTrue(hasattr(files[0], 'Id'))
- self.assertEqual(len(files), 1)
-
+ self.assertEqual(len(files), 1)
+
def testGetAppResultFiles(self):
- files = self.api.getAppResultFiles(tconst['appresult_id'])
+ files = self.api.getAppResultFiles(tconst['appresult_id'])
self.assertTrue(hasattr(files[0], 'Id'))
-
+
def testGetAppResultFilesWithQp(self):
- files = self.api.getAppResultFiles(tconst['appresult_id'], qp({'Limit':1}))
+ files = self.api.getAppResultFiles(tconst['appresult_id'], qp({'Limit':1}))
self.assertTrue(hasattr(files[0], 'Id'))
- self.assertEqual(len(files), 1)
+ self.assertEqual(len(files), 1)
def testGetAppResultsByProject(self):
appresults = self.api.getAppResultsByProject(tconst['project_id'])
self.assertTrue(hasattr(appresults[0], 'Id'))
-
+
def testGetAppResultsByProjectWithQp(self):
appresults = self.api.getAppResultsByProject(tconst['project_id'], qp({'Limit':1}))
self.assertTrue(hasattr(appresults[0], 'Id'))
self.assertEqual(len(appresults), 1)
-
+
def testGetAppResultsByProjectWithStatusesArg(self):
appresults = self.api.getAppResultsByProject(tconst['project_id'], statuses=['complete'])
self.assertTrue(hasattr(appresults[0], 'Id'))
-
+
def testCreateAppResultNewAppSsn(self):
'''
Create a new 'unit test' project, or get it if exists.
- Create a new app result that creates a new app ssn.
+ Create a new app result that creates a new app ssn.
'''
- proj = self.api.createProject(tconst['create_project_name'])
- ar = self.api.createAppResult(proj.Id, name="test create appresult new ssn",
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = self.api.createAppResult(proj.Id, name="test create appresult new ssn",
desc="test create appresult new ssn", appSessionId="")
- self.assertTrue(hasattr(ar, 'Id'))
+ self.assertTrue(hasattr(ar, 'Id'))
def testCreateAppResultCredentialsAppSsn(self):
'''
@@ -733,69 +736,69 @@ def testCreateAppResultCredentialsAppSsn(self):
then create a new api obj with the new ssn,
then create an appresult in the new ssn
'''
- proj = self.api.createProject(tconst['create_project_name'])
- ar = self.api.createAppResult(proj.Id, name="test create appresult creds ssn",
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = self.api.createAppResult(proj.Id, name="test create appresult creds ssn",
desc="test create appresult creds ssn", appSessionId="")
#url = urlparse(self.api.apiClient.apiServer)
#newApiServer = url.scheme + "://" + url.netloc
- #new_api = BaseSpaceAPI(self.api.key, self.api.secret, newApiServer,
+ #new_api = BaseSpaceAPI(self.api.key, self.api.secret, newApiServer,
new_api = BaseSpaceAPI(self.api.key, self.api.secret, self.api.apiServer,
self.api.version, ar.AppSession.Id, self.api.getAccessToken())
- ar2 = new_api.createAppResult(proj.Id, name="test create appresult creds ssn 2",
+ ar2 = new_api.createAppResult(proj.Id, name="test create appresult creds ssn 2",
desc="test create appresult creds ssn 2")
self.assertTrue(hasattr(ar2, 'Id'))
-
+
def testCreateAppResultProvidedAppSsn(self):
'''
Create a new app result that creates a new app ssn,
then create a new api obj with the new ssn,
then create an appresult in the new ssn
'''
- proj = self.api.createProject(tconst['create_project_name'])
- ar = self.api.createAppResult(proj.Id, name="test create appresult provided ssn",
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = self.api.createAppResult(proj.Id, name="test create appresult provided ssn",
desc="test create appresult provided ssn", appSessionId="")
- ar2 = self.api.createAppResult(proj.Id, name="test create appresult provided ssn 2",
+ ar2 = self.api.createAppResult(proj.Id, name="test create appresult provided ssn 2",
desc="test create appresult provided ssn 2", appSessionId=ar.AppSession.Id)
self.assertTrue(hasattr(ar2, 'Id'))
-
- # Note that appResultFileUpload() is tested with other file upload methods
+
+ # Note that appResultFileUpload() is tested with other file upload methods
# (in a separate suite: TestAPIUploadMethods)
-
+
class TestRunMethods(TestCase):
'''
Tests Run object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
- self.run = self.api.getRunById(tconst['run_id'])
+ self.run = self.api.getRunById(tconst['run_id'])
- def testIsInit(self):
+ def testIsInit(self):
self.assertEqual(self.run.isInit(), True)
-
+
def testIsInitException(self):
run = Run.Run()
with self.assertRaises(ModelNotInitializedException):
- run.isInit()
+ run.isInit()
def testGetAccessString(self):
self.assertEqual(self.run.getAccessStr(), 'write run ' + self.run.Id)
-
+
def testGetAccessStringWithArg(self):
self.assertEqual(self.run.getAccessStr('read'), 'read run ' + self.run.Id)
def testRunGetFiles(self):
- rf = self.run.getFiles(self.api)
+ rf = self.run.getFiles(self.api)
self.assertTrue(hasattr(rf[0], 'Id'))
-
+
def testRunGetFilesWithQp(self):
- rf = self.run.getFiles(self.api, qp({'Limit':200}))
+ rf = self.run.getFiles(self.api, qp({'Limit':200}))
self.assertTrue(hasattr(rf[0], 'Id'))
self.assertEqual(len(rf), 200)
def testRunSamples(self):
- rs = self.run.getSamples(self.api)
+ rs = self.run.getSamples(self.api)
self.assertTrue(hasattr(rs[0], 'Id'))
-
+
def testRunSamplesWithQp(self):
rs = self.run.getSamples(self.api, qp({'Limit':1}))
self.assertTrue(hasattr(rs[0], 'Id'))
@@ -804,8 +807,8 @@ def testRunSamplesWithQp(self):
class TestAPIRunMethods(TestCase):
'''
Tests API object Run methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
def testGetAccessibleRunsByUser(self):
@@ -816,37 +819,37 @@ def testGetAccessibleRunsByUserWithQp(self):
runs = self.api.getAccessibleRunsByUser(qp({'Limit':500}))
run = next(r for r in runs if r.Id == tconst['run_id'])
self.assertTrue(run.Id, tconst['run_id'])
-
- def testGetRunById(self):
- rf = self.api.getRunById(tconst['run_id'])
+
+ def testGetRunById(self):
+ rf = self.api.getRunById(tconst['run_id'])
self.assertEqual(rf.Id, tconst['run_id'])
-
- def testGetRunByIdWithQp(self):
- rf = self.api.getRunById(tconst['run_id'], qp({'Limit':1})) # limit doesn't make much sense here
+
+ def testGetRunByIdWithQp(self):
+ rf = self.api.getRunById(tconst['run_id'], qp({'Limit':1})) # limit doesn't make much sense here
self.assertEqual(rf.Id, tconst['run_id'])
-
- def testGetRunPropertiesById(self):
- props = self.api.getRunPropertiesById(tconst['run_id'])
- self.assertTrue(hasattr(props, 'TotalCount'))
-
- def testGetRunPropertiesByIdWithQp(self):
- props = self.api.getRunPropertiesById(tconst['run_id'], qp({'Limit':1}))
+
+ def testGetRunPropertiesById(self):
+ props = self.api.getRunPropertiesById(tconst['run_id'])
+ self.assertTrue(hasattr(props, 'TotalCount'))
+
+ def testGetRunPropertiesByIdWithQp(self):
+ props = self.api.getRunPropertiesById(tconst['run_id'], qp({'Limit':1}))
self.assertTrue(hasattr(props, 'TotalCount'))
self.assertEqual(len(props.Items), 1)
-
- def testGetRunFilesById(self):
- rf = self.api.getRunFilesById(tconst['run_id'])
+
+ def testGetRunFilesById(self):
+ rf = self.api.getRunFilesById(tconst['run_id'])
self.assertTrue(hasattr(rf[0], 'Id'))
-
+
def testGetRunFilesByIdWithQp(self):
rf = self.api.getRunFilesById(tconst['run_id'], qp({'Limit':1}))
self.assertTrue(hasattr(rf[0], 'Id'))
- self.assertEqual(len(rf), 1)
+ self.assertEqual(len(rf), 1)
def testRunSamplesById(self):
- rs = self.api.getRunSamplesById(tconst['run_id'])
+ rs = self.api.getRunSamplesById(tconst['run_id'])
self.assertTrue(hasattr(rs[0], 'Id'))
-
+
def testRunSamplesByIdWithQp(self):
rs = self.api.getRunSamplesById(tconst['run_id'], qp({'Limit':1}))
self.assertTrue(hasattr(rs[0], 'Id'))
@@ -855,35 +858,35 @@ def testRunSamplesByIdWithQp(self):
class TestSampleMethods(TestCase):
'''
Tests Sample object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
self.sample = self.api.getSampleById(tconst['sample_id'])
-
- def testIsInit(self):
+
+ def testIsInit(self):
self.assertEqual(self.sample.isInit(), True)
-
+
def testIsInitException(self):
sample = Sample.Sample()
with self.assertRaises(ModelNotInitializedException):
- sample.isInit()
+ sample.isInit()
def testGetAccessString(self):
self.assertEqual(self.sample.getAccessStr(), 'write sample ' + self.sample.Id)
-
+
def testGetAccessStringWithArg(self):
self.assertEqual(self.sample.getAccessStr('read'), 'read sample ' + self.sample.Id)
-
+
def testGetReferencedAppResults(self):
ars = self.sample.getReferencedAppResults(self.api)
self.assertTrue(hasattr(ars[0], 'Id'), "Referenced AppResult should have an Id (assuming this Sample has been analyzed)")
-
+
def testGetFiles(self):
- files = self.sample.getFiles(self.api)
+ files = self.sample.getFiles(self.api)
self.assertTrue(hasattr(files[0], "Id"))
def testGetFilesWithQp(self):
- files = self.sample.getFiles(self.api, qp({'Limit':1}))
+ files = self.sample.getFiles(self.api, qp({'Limit':1}))
self.assertTrue(hasattr(files[0], "Id"))
self.assertEqual(len(files), 1)
@@ -900,10 +903,10 @@ def testUploadFile(self):
testDir = "testLargeUploadSampleDirectory"
fileName = os.path.basename(tconst['file_large_upload'])
myFile = s.uploadFile(
- api=self.api,
- localPath=tconst['file_large_upload'],
- fileName=fileName,
- directory=testDir,
+ api=self.api,
+ localPath=tconst['file_large_upload'],
+ fileName=fileName,
+ directory=testDir,
contentType=tconst['file_large_upload_content_type'])
self.assertEqual(myFile.Path, os.path.join(testDir, fileName))
self.assertEqual(myFile.Size, tconst['file_large_upload_size'])
@@ -920,7 +923,7 @@ class TestAPISampleMethods(TestCase):
'''
def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
-
+
def testGetSamplesByProject(self):
samples = self.api.getSamplesByProject(tconst['project_id'])
self.assertIsInstance(int(samples[0].Id), int)
@@ -928,29 +931,29 @@ def testGetSamplesByProject(self):
def testGetSamplesByProjectWithQp(self):
samples = self.api.getSamplesByProject(tconst['project_id'], qp({'Limit':1}))
self.assertIsInstance(int(samples[0].Id), int)
- self.assertEqual(len(samples), 1)
+ self.assertEqual(len(samples), 1)
- def testGetSampleById(self):
+ def testGetSampleById(self):
sample = self.api.getSampleById(tconst['sample_id'])
self.assertEqual(sample.Id, tconst['sample_id'])
- def testGetSampleByIdWithQp(self):
+ def testGetSampleByIdWithQp(self):
sample = self.api.getSampleById(tconst['sample_id'], qp({'Limit':1})) # Limit doesn't make much sense here
- self.assertEqual(sample.Id, tconst['sample_id'])
-
+ self.assertEqual(sample.Id, tconst['sample_id'])
+
def testGetSamplePropertiesById(self):
props = self.api.getSamplePropertiesById(tconst['sample_id'])
- self.assertTrue(hasattr(props, 'TotalCount'))
+ self.assertTrue(hasattr(props, 'TotalCount'))
def testGetSamplePropertiesByIdWithQp(self):
props = self.api.getSamplePropertiesById(tconst['sample_id'], qp({'Limit':1}))
- self.assertTrue(hasattr(props, 'TotalCount'))
+ self.assertTrue(hasattr(props, 'TotalCount'))
self.assertEqual(len(props.Items), 1)
-
+
def testGetSampleFilesById(self):
files = self.api.getSampleFilesById(tconst['sample_id'])
self.assertTrue(hasattr(files[0], 'Id'))
-
+
def testGetSampleFilesByIdWithQp(self):
files = self.api.getSampleFilesById(tconst['sample_id'], qp({'Limit':1}))
self.assertTrue(hasattr(files[0], 'Id'))
@@ -963,10 +966,10 @@ class TestProjectMethods(TestCase):
def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
self.project = self.api.getProjectById(tconst['project_id'])
-
- def testIsInit(self):
+
+ def testIsInit(self):
self.assertEqual(self.project.isInit(), True)
-
+
def testIsInitException(self):
project = Project.Project()
with self.assertRaises(ModelNotInitializedException):
@@ -974,14 +977,14 @@ def testIsInitException(self):
def testGetAccessString(self):
self.assertEqual(self.project.getAccessStr(), 'write project ' + self.project.Id)
-
+
def testGetAccessStringWithArg(self):
- self.assertEqual(self.project.getAccessStr('read'), 'read project ' + self.project.Id)
-
+ self.assertEqual(self.project.getAccessStr('read'), 'read project ' + self.project.Id)
+
def testGetAppResults(self):
appresults = self.project.getAppResults(self.api)
self.assertTrue(hasattr(appresults[0], 'Id'))
-
+
def testGetAppResultsWithOptionalArgs(self):
appresults = self.project.getAppResults(self.api, qp({'Limit':1}), statuses=['complete'])
self.assertTrue(hasattr(appresults[0], 'Id'))
@@ -990,7 +993,7 @@ def testGetAppResultsWithOptionalArgs(self):
def testGetSamples(self):
samples = self.project.getSamples(self.api)
self.assertIsInstance(int(samples[0].Id), int)
-
+
def testGetSamplesWithOptionalArgs(self):
samples = self.project.getSamples(self.api, qp({'Limit':1}))
self.assertIsInstance(int(samples[0].Id), int)
@@ -1003,27 +1006,27 @@ def testCreateAppResult(self):
then create a new api obj with the new ssn,
then create an appresult in the new ssn
'''
- proj = self.api.createProject(tconst['create_project_name'])
- ar = proj.createAppResult(self.api, name="test create appresult creds ssn, project obj",
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = proj.createAppResult(self.api, name="test create appresult creds ssn, project obj",
desc="test create appresult creds ssn, project obj", appSessionId="")
#url = urlparse(self.api.apiClient.apiServer)
- #newApiServer = url.scheme + "://" + url.netloc
- #new_api = BaseSpaceAPI(self.api.key, self.api.secret, newApiServer,
- new_api = BaseSpaceAPI(self.api.key, self.api.secret, self.api.apiServer,
+ #newApiServer = url.scheme + "://" + url.netloc
+ #new_api = BaseSpaceAPI(self.api.key, self.api.secret, newApiServer,
+ new_api = BaseSpaceAPI(self.api.key, self.api.secret, self.api.apiServer,
self.api.version, ar.AppSession.Id, self.api.getAccessToken())
- ar2 = proj.createAppResult(new_api, name="test create appresult creds ssn, project obj 2",
+ ar2 = proj.createAppResult(new_api, name="test create appresult creds ssn, project obj 2",
desc="test create appresult creds ssn, proejct obj 2")
- self.assertTrue(hasattr(ar2, 'Id'))
+ self.assertTrue(hasattr(ar2, 'Id'))
def testCreateAppResultWithOptionalArgs(self):
'''
Create a new 'unit test' project, or get it if exists.
- Create a new app result that creates a new app ssn.
+ Create a new app result that creates a new app ssn.
'''
- proj = self.api.createProject(tconst['create_project_name'])
- ar = proj.createAppResult(self.api, name="test create appresult new ssn, project obj",
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = proj.createAppResult(self.api, name="test create appresult new ssn, project obj",
desc="test create appresult new ssn, project obj", samples=[], appSessionId="")
- self.assertTrue(hasattr(ar, 'Id'))
+ self.assertTrue(hasattr(ar, 'Id'))
def testCreateSample(self):
'''
@@ -1032,7 +1035,7 @@ def testCreateSample(self):
then create a new api obj with the new ssn,
then create a sample in the new ssn
'''
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
s = proj.createSample(self.api, "SRA123456", "SRA Import", 1,
tconst['create_sample_name'], [tconst['read_length']],
tconst['raw_count'], tconst['PF_count'], appSessionId="")
@@ -1046,80 +1049,80 @@ def testCreateSample(self):
class TestAPIProjectMethods(TestCase):
'''
Tests API Project object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
def testCreateProject(self):
proj = self.api.createProject(tconst['create_project_name'])
- self.assertEqual(proj.Name, tconst['create_project_name'])
-
+ self.assertEqual(proj.Name, tconst['create_project_name'])
+
def testGetProjectById(self):
proj = self.api.getProjectById(tconst['project_id'])
self.assertEqual(proj.Id, tconst['project_id'])
def testGetProjectByIdWithQp(self):
proj = self.api.getProjectById(tconst['project_id'], qp({'Limit':1})) # Limit doesn't make sense here
- self.assertEqual(proj.Id, tconst['project_id'])
+ self.assertEqual(proj.Id, tconst['project_id'])
def testGetProjectPropertiesById(self):
props = self.api.getProjectPropertiesById(tconst['project_id'])
- self.assertTrue(hasattr(props, 'TotalCount'))
+ self.assertTrue(hasattr(props, 'TotalCount'))
def testGetProjectPropertiesByIdWithQp(self):
- props = self.api.getProjectPropertiesById(tconst['project_id'], qp({'Limit':1}))
- self.assertTrue(hasattr(props, 'TotalCount'))
+ props = self.api.getProjectPropertiesById(tconst['project_id'], qp({'Limit':1}))
+ self.assertTrue(hasattr(props, 'TotalCount'))
# test project has no properties, so can't test Limit
def testGetProjectByUser(self):
- projects = self.api.getProjectByUser()
+ projects = self.api.getProjectByUser()
self.assertTrue(hasattr(projects[0], 'Id'))
-
+
def testGetProjectByUserWithQp(self):
- projects = self.api.getProjectByUser(qp({'Limit':1}))
- self.assertTrue(hasattr(projects[0], 'Id'))
+ projects = self.api.getProjectByUser(qp({'Limit':1}))
+ self.assertTrue(hasattr(projects[0], 'Id'))
class TestUserMethods(TestCase):
'''
Tests User object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
self.user = self.api.getUserById('current')
-
- def testIsInit(self):
+
+ def testIsInit(self):
self.assertEqual(self.user.isInit(), True)
-
+
def testIsInitException(self):
user = User.User()
with self.assertRaises(ModelNotInitializedException):
user.isInit()
-
+
def testGetProjects(self):
- projects = self.user.getProjects(self.api)
+ projects = self.user.getProjects(self.api)
self.assertTrue(hasattr(projects[0], 'Id'))
-
+
def testGetProjectsWithQp(self):
- projects = self.user.getProjects(self.api, queryPars=qp({'Limit':1}))
+ projects = self.user.getProjects(self.api, queryPars=qp({'Limit':1}))
self.assertTrue(hasattr(projects[0], 'Id'))
self.assertTrue(len(projects), 1)
-
+
def testGetRuns(self):
- runs = self.user.getRuns(self.api)
+ runs = self.user.getRuns(self.api)
self.assertTrue(hasattr(runs[0], 'Id'))
-
+
def testGetRunsWithQp(self):
- runs = self.user.getRuns(self.api, queryPars=qp({'Limit':1}))
+ runs = self.user.getRuns(self.api, queryPars=qp({'Limit':1}))
self.assertTrue(hasattr(runs[0], 'Id'))
self.assertTrue(len(runs), 1)
class TestAPIUserMethods(TestCase):
'''
Tests API User object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
-
+
def testGetUserById(self):
user = self.api.getUserById('current')
self.assertTrue(hasattr(user, 'Id'), 'User object should contain Id attribute')
@@ -1127,32 +1130,32 @@ def testGetUserById(self):
class TestFileMethods(TestCase):
'''
Tests File object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
self.file = self.api.getFileById(tconst['file_id_small'])
-
- def testIsInit(self):
+
+ def testIsInit(self):
self.assertEqual(self.file.isInit(), True)
-
+
def testIsInitException(self):
file = File.File()
with self.assertRaises(ModelNotInitializedException):
file.isInit()
-
- # not testing isValidFileOption() -- deprecated
+
+ # not testing isValidFileOption() -- deprecated
# downloadFile() is tested in a separate suite
-
+
def testGetFileUrl(self):
url = self.file.getFileUrl(self.api)
url_parts = urlparse(url)
self.assertEqual(url_parts.scheme, 'https')
-
+
def testGetFileS3metadata(self):
- meta = self.file.getFileS3metadata(self.api)
+ meta = self.file.getFileS3metadata(self.api)
self.assertTrue('url' in meta)
- self.assertTrue('etag' in meta)
+ self.assertTrue('etag' in meta)
def testGetIntervalCoverage(self):
bam = self.api.getFileById(tconst['bam_file_id'])
@@ -1168,21 +1171,21 @@ def testGetCoverageMeta(self):
cov_meta = bam.getCoverageMeta(
self.api,
Chrom = tconst['bam_cov_chr_name'] )
- self.assertTrue(hasattr(cov_meta, 'MaxCoverage'))
-
+ self.assertTrue(hasattr(cov_meta, 'MaxCoverage'))
+
def testFilterVariant(self):
vcf = self.api.getFileById(tconst['vcf_file_id'])
vars = vcf.filterVariant(
- self.api,
+ self.api,
Chrom = tconst['vcf_chr_name'],
StartPos = tconst['vcf_start_coord'],
- EndPos = tconst['vcf_end_coord'], )
+ EndPos = tconst['vcf_end_coord'], )
self.assertEqual(vars[0].CHROM, tconst['vcf_chr_name'])
-
+
def testFilterVariantWithQp(self):
vcf = self.api.getFileById(tconst['vcf_file_id'])
vars = vcf.filterVariant(
- self.api,
+ self.api,
Chrom = tconst['vcf_chr_name'],
StartPos = tconst['vcf_start_coord'],
EndPos = tconst['vcf_end_coord'],
@@ -1190,18 +1193,18 @@ def testFilterVariantWithQp(self):
queryPars = qp({'Limit':1}) )
self.assertEqual(vars[0].CHROM, tconst['vcf_chr_name'])
self.assertEqual(len(vars), 1)
-
+
def testFilterVariantReturnVCFString(self):
vcf = self.api.getFileById(tconst['vcf_file_id'])
with self.assertRaises(NotImplementedError): # for now...
vars = vcf.filterVariant(
- self.api,
+ self.api,
Chrom = tconst['vcf_chr_name'],
StartPos = tconst['vcf_start_coord'],
EndPos = tconst['vcf_end_coord'],
Format = 'vcf')
- #self.assertEqual(type(vars), str)
-
+ #self.assertEqual(type(vars), str)
+
def testGetVariantMeta(self):
vcf = self.api.getFileById(tconst['vcf_file_id'])
hdr = vcf.getVariantMeta(self.api)
@@ -1216,22 +1219,22 @@ def testGetVariantMetaReturnVCFString(self):
class TestAPIFileMethods(TestCase):
'''
Tests API File object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
-
+
def testGetFileById(self):
file = self.api.getFileById(tconst['file_id_small'])
self.assertTrue(file.Id, tconst['file_id_small'])
def testGetFileByIdWithQp(self):
file = self.api.getFileById(tconst['file_id_small'], qp({'Limit':1})) # Limit doesn't make much sense here
- self.assertEqual(file.Id, tconst['file_id_small'])
+ self.assertEqual(file.Id, tconst['file_id_small'])
def testGetFilesBySample(self):
files = self.api.getFilesBySample(tconst['sample_id'])
self.assertTrue(hasattr(files[0], 'Id'))
-
+
def testGetFilesBySampleWithQp(self):
files = self.api.getFilesBySample(tconst['sample_id'], qp({'Limit':1}))
self.assertTrue(hasattr(files[0], 'Id'))
@@ -1240,7 +1243,7 @@ def testGetFilesBySampleWithQp(self):
def testGetFilePropertiesById(self):
props = self.api.getFilePropertiesById(tconst['file_id_small'])
self.assertTrue(hasattr(props, 'TotalCount'))
-
+
def testGetFilePropertiesByIdWithQp(self):
props = self.api.getFilePropertiesById(tconst['file_id_small'], qp({'Limit':1}))
self.assertTrue(hasattr(props, 'TotalCount'))
@@ -1250,45 +1253,45 @@ def testFileUrl(self):
url = self.api.fileUrl(tconst['file_id_small'])
url_parts = urlparse(url)
self.assertEqual(url_parts.scheme, 'https')
-
+
def testFileS3metadata(self):
- meta = self.api.fileS3metadata(tconst['file_id_small'])
+ meta = self.api.fileS3metadata(tconst['file_id_small'])
self.assertTrue('url' in meta)
self.assertTrue('etag' in meta)
- # api file upload/download methods are tested in a separate suite:
- # __initiateMultipartFileUpload__()
- # __uploadMultipartUnit__()
- # __finalizeMultipartFileUpload__()
- # __singlepartFileUpload__()
- # multipartFileUpload()
-
+ # api file upload/download methods are tested in a separate suite:
+ # __initiateMultipartFileUpload__()
+ # __uploadMultipartUnit__()
+ # __finalizeMultipartFileUpload__()
+ # __singlepartFileUpload__()
+ # multipartFileUpload()
+
# __downloadFile__()
# fileDownload()
- # multipartFileDownload()
+ # multipartFileDownload()
class TestAppSessionSemiCompactMethods(TestCase):
'''
Tests AppSessionSemiCompact object methods
- '''
+ '''
@classmethod
- def setUpClass(cls):
+ def setUpClass(cls):
cls.api = BaseSpaceAPI(profile='unit_tests')
# create an app session, since the client key and secret must match those of the ssn application
- cls.proj = cls.api.createProject(tconst['create_project_name'])
+ cls.proj = cls.api.createProject(tconst['create_project_name'])
cls.ar = cls.proj.createAppResult(cls.api, "test AppSessionSemiCompact Methods", "test AppSessionSemiCompact Methods", appSessionId="")
cls.ssn = cls.ar.AppSession # this is an AppSessionSemiCompact instance
- def testIsInit(self):
+ def testIsInit(self):
self.assertEqual(self.ssn.isInit(), True)
-
+
def testIsInitException(self):
- ssn = AppSessionSemiCompact.AppSessionSemiCompact()
+ ssn = AppSessionSemiCompact.AppSessionSemiCompact()
with self.assertRaises(ModelNotInitializedException):
- ssn.isInit()
-
+ ssn.isInit()
+
def testCanWorkOn(self):
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test canWorkOn()", "test canWorkOn()", appSessionId="")
self.assertEqual(ar.AppSession.canWorkOn(), True)
ar.AppSession.setStatus(self.api, 'NeedsAttention', "Will you look into this?")
@@ -1296,28 +1299,28 @@ def testCanWorkOn(self):
ar.AppSession.setStatus(self.api, 'TimedOut', "This is taking forever")
self.assertEqual(ar.AppSession.canWorkOn(), True)
ar.AppSession.setStatus(self.api, 'Complete', "Time to wrap things up")
- self.assertEqual(ar.AppSession.canWorkOn(), False)
+ self.assertEqual(ar.AppSession.canWorkOn(), False)
def testCanWorkOn_Aborted(self):
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test canWorkOn() Aborted", "test canWorkOn() Aborted", appSessionId="")
self.assertEqual(ar.AppSession.canWorkOn(), True)
ar.AppSession.setStatus(self.api, 'Aborted', "Abandon Ship!")
- self.assertEqual(ar.AppSession.canWorkOn(), False)
-
+ self.assertEqual(ar.AppSession.canWorkOn(), False)
+
def setStatus(self):
status = 'Complete'
statusSummary = "Let's go home now"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setStatus()", "test setStatus()", appSessionId="")
ar.AppSession.setStatus(self.api, status, statusSummary)
self.assertEqual(ar.AppSession.Status, status)
self.assertEqual(ar.AppSession.StatusSummary, statusSummary)
-
+
def testSetStatus_CompleteStatusException(self):
status = 'Complete'
statusSummary = "Let's go"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setStatus() Complete exception", "test setStatus() Complete exception", appSessionId="")
ar.AppSession.setStatus(self.api, status, statusSummary)
status = 'Aborted'
@@ -1328,36 +1331,36 @@ def testSetStatus_CompleteStatusException(self):
def testSetStatus_AbortedStatusException(self):
status = 'Aborted'
statusSummary = "Let's go"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setStatus() aborted exception", "test setStatus() aborted exception", appSessionId="")
ar.AppSession.setStatus(self.api, status, statusSummary)
status = 'Running'
statusSummary = "I thought everything was peachy?"
with self.assertRaises(AppSessionException):
ar.AppSession.setStatus(self.api, status, statusSummary)
-
+
class TestAppSessionMethods(TestCase):
'''
Tests AppSession object methods
- '''
+ '''
@classmethod
- def setUpClass(cls):
+ def setUpClass(cls):
cls.api = BaseSpaceAPI(profile='unit_tests')
# create an app session, since the client key and secret must match those of the ssn application
- cls.proj = cls.api.createProject(tconst['create_project_name'])
+ cls.proj = cls.api.createProject(tconst['create_project_name'])
cls.ar = cls.proj.createAppResult(cls.api, "test AppSession Methods", "test AppSession Methods", appSessionId="")
- cls.ssn = cls.api.getAppSessionById(cls.ar.AppSession.Id) # this is an AppSession instance
+ cls.ssn = cls.api.getAppSessionById(cls.ar.AppSession.Id) # this is an AppSession instance
- def testIsInit(self):
+ def testIsInit(self):
self.assertEqual(self.ssn.isInit(), True)
-
+
def testIsInitException(self):
- ssn = AppSession.AppSession()
+ ssn = AppSession.AppSession()
with self.assertRaises(ModelNotInitializedException):
- ssn.isInit()
-
+ ssn.isInit()
+
def testCanWorkOn(self):
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test canWorkOn()", "test canWorkOn()", appSessionId="")
ssn = self.api.getAppSessionById(ar.AppSession.Id)
self.assertEqual(ssn.canWorkOn(), True)
@@ -1366,32 +1369,32 @@ def testCanWorkOn(self):
ssn.setStatus(self.api, 'TimedOut', "This is taking forever")
self.assertEqual(ssn.canWorkOn(), True)
ssn.setStatus(self.api, 'Complete', "Time to wrap things up")
- self.assertEqual(ssn.canWorkOn(), False)
+ self.assertEqual(ssn.canWorkOn(), False)
def testCanWorkOn_Aborted(self):
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test canWorkOn() Aborted", "test canWorkOn() Aborted", appSessionId="")
ssn = self.api.getAppSessionById(ar.AppSession.Id)
self.assertEqual(ssn.canWorkOn(), True)
ssn.setStatus(self.api, 'Aborted', "Abandon Ship!")
- self.assertEqual(ssn.canWorkOn(), False)
-
+ self.assertEqual(ssn.canWorkOn(), False)
+
def setStatus(self):
status = 'Complete'
statusSummary = "Let's go home now"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setStatus()", "test setStatus()", appSessionId="")
ssn = self.api.getAppSessionById(ar.AppSession.Id)
ssn.setStatus(self.api, status, statusSummary)
self.assertEqual(ssn.Status, status)
self.assertEqual(ssn.StatusSummary, statusSummary)
-
+
def testSetStatus_CompleteStatusException(self):
status = 'Complete'
statusSummary = "Let's go"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setStatus() Complete exception", "test setStatus() Complete exception", appSessionId="")
- ssn = self.api.getAppSessionById(ar.AppSession.Id)
+ ssn = self.api.getAppSessionById(ar.AppSession.Id)
ssn.setStatus(self.api, status, statusSummary)
status = 'Aborted'
statusSummary = '(Too) late breaking changes'
@@ -1401,19 +1404,19 @@ def testSetStatus_CompleteStatusException(self):
def testSetStatus_AbortedStatusException(self):
status = 'Aborted'
statusSummary = "Let's go"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setStatus() aborted exception", "test setStatus() aborted exception", appSessionId="")
- ssn = self.api.getAppSessionById(ar.AppSession.Id)
+ ssn = self.api.getAppSessionById(ar.AppSession.Id)
ssn.setStatus(self.api, status, statusSummary)
status = 'Running'
statusSummary = "I thought everything was peachy?"
with self.assertRaises(AppSessionException):
ssn.setStatus(self.api, status, statusSummary)
-
- def test__deserializeReferences__(self):
+
+ def test__deserializeReferences__(self):
asla = AppSessionLaunchObject.AppSessionLaunchObject()
asla.Type = 'Project'
- asla.Content = { "Id": "123",
+ asla.Content = { "Id": "123",
"UserOwnedBy": {"Id": "321",
"Href": "v1pre3/users/321",
"Name": "Jay Flatley" },
@@ -1429,44 +1432,44 @@ class TestAppSessionLaunchObjectMethods(TestCase):
'''
Tests AppSessionLaunchObject object methods
'''
- def setUp(self):
- self.api = BaseSpaceAPI(profile='unit_tests')
-
+ def setUp(self):
+ self.api = BaseSpaceAPI(profile='unit_tests')
+
def test__deserializeObject__(self):
asla = AppSessionLaunchObject.AppSessionLaunchObject()
asla.Type = 'Project'
- asla.Content = { "Id": "123",
+ asla.Content = { "Id": "123",
"UserOwnedBy": {"Id": "321",
"Href": "v1pre3/users/321",
"Name": "Jay Flatley" },
"Href": "v1pre3/projects/123",
"Name": "Project Boomtown",
- "DataCreated": "2020-01-01T01:01:01.0000000" }
+ "DataCreated": "2020-01-01T01:01:01.0000000" }
asla.__deserializeObject__(self.api)
self.assertEqual(asla.Content.Id, "123")
-
+
class TestAPIAppSessionMethods(TestCase):
'''
Tests API AppSession object methods
- '''
+ '''
@classmethod
- def setUpClass(cls):
+ def setUpClass(cls):
cls.api = BaseSpaceAPI(profile='unit_tests')
# create an app session, since the client key and secret must match those of the ssn application
- cls.proj = cls.api.createProject(tconst['create_project_name'])
+ cls.proj = cls.api.createProject(tconst['create_project_name'])
cls.ar = cls.proj.createAppResult(cls.api, "test API AppSession Methods", "test API AppSession Methods", appSessionId="")
cls.ssn = cls.ar.AppSession
- def test__deserializeAppSessionResponse__(self):
+ def test__deserializeAppSessionResponse__(self):
# very similar to 2nd half of BaseAPI.__singleRequest__()
references = [ { "Type": "Project",
- "Href": "v1pre3/projects/321",
- "Content": {"Id": "321", } } ]
+ "Href": "v1pre3/projects/321",
+ "Content": {"Id": "321", } } ]
ssn_dict = { "ResponseStatus": {},
- "Notifications": {},
+ "Notifications": {},
"Response": {"Id": "123",
"Href": "v1pre3/appsessions/123",
- "References": references, } }
+ "References": references, } }
ssn = self.api.__deserializeAppSessionResponse__(ssn_dict)
self.assertEqual(ssn.Id, "123")
self.assertEqual(ssn.References[0].Content.Id, "321")
@@ -1474,9 +1477,9 @@ def test__deserializeAppSessionResponse__(self):
def test__deserializeAppSessionResponse__ErrorCodeException(self):
ssn_dict = { "ResponseStatus": { "ErrorCode": "666", "Message": "We are dying" } }
with self.assertRaises(AppSessionException):
- self.api.__deserializeAppSessionResponse__(ssn_dict)
-
- def testGetAppSessionById(self):
+ self.api.__deserializeAppSessionResponse__(ssn_dict)
+
+ def testGetAppSessionById(self):
ssn = self.api.getAppSessionById(self.ssn.Id)
self.assertEqual(ssn.Id, self.ssn.Id)
@@ -1491,12 +1494,11 @@ def testGetAppSessionWithId(self):
def testGetAppSessionPropertiesById(self):
props = self.api.getAppSessionPropertiesById(self.ssn.Id)
- self.assertTrue(any((prop.Items[0].Id == self.ar.Id) for prop in props.Items if prop.Name == "Output.AppResults"))
+ self.assertTrue(any((prop.Items[0].Id == self.ar.Id) for prop in props.Items if prop.Name == "Output.AppResults"))
def testGetAppSessionPropertiesByIdWithQp(self):
props = self.api.getAppSessionPropertiesById(self.ssn.Id, qp({'Limit':1}))
- self.assertTrue(any((prop.Items[0].Id == self.ar.Id) for prop in props.Items if prop.Name == "Output.AppResults"))
- self.assertEqual(len(props.Items), 1)
+ self.assertEqual(len(props.Items), 1)
def testGetAppSessionPropertyByName(self):
prop = self.api.getAppSessionPropertyByName(self.ssn.Id, 'Output.AppResults')
@@ -1510,12 +1512,20 @@ def testGetAppSessionPropertyByNameWithQp(self):
def testGetAppSessionInputsById(self):
props = self.api.getAppSessionInputsById(self.ssn.Id)
- self.assertEqual(len(props), 0)
+ self.assertEqual(len(props), 1)
+ self.assertEqual("Samples" in props, True)
+ self.assertEqual(len(props["Samples"].Items), 0)
+ # NB: these have changed from previous versions of the unit tests
+ # because it looks like appsessions created through the API now have an (empty) input samples list by default
# TODO can't test this easily since self-created ssn don't have inputs. Add POST properties for ssns, and manually add an 'Input.Test' property, then test for it?
-
+
def testGetAppSessionInputsByIdWithQp(self):
props = self.api.getAppSessionInputsById(self.ssn.Id, qp({'Limit':1}))
- self.assertEqual(len(props), 0)
+ self.assertEqual(len(props), 1)
+ self.assertEqual("Samples" in props, True)
+ self.assertEqual(len(props["Samples"].Items), 0)
+ # NB: these have changed from previous versions of the unit tests
+ # because it looks like appsessions created through the API now have an (empty) input samples list by default
# TODO same as test above
def testSetAppSessionState_UpdatedStatus(self):
@@ -1524,11 +1534,11 @@ def testSetAppSessionState_UpdatedStatus(self):
ssn = self.api.setAppSessionState(self.ssn.Id, status, statusSummary)
self.assertEqual(ssn.Status, status)
self.assertEqual(ssn.StatusSummary, statusSummary)
-
+
def testSetAppSessionStateToComplete(self):
status = 'Complete'
statusSummary = 'things are looking good'
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setAppSessionState to " + status, "test setAppSessionState to " + status, appSessionId="")
ssn = self.api.setAppSessionState(ar.AppSession.Id, status, statusSummary)
self.assertEqual(ssn.Status, status)
@@ -1537,16 +1547,16 @@ def testSetAppSessionStateToComplete(self):
def testSetAppSessionStateToNeedsAttention(self):
status = 'NeedsAttention'
statusSummary = 'things are looking shaky'
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setAppSessionState to " + status, "test setAppSessionState to " + status, appSessionId="")
ssn = self.api.setAppSessionState(ar.AppSession.Id, status, statusSummary)
self.assertEqual(ssn.Status, status)
self.assertEqual(ssn.StatusSummary, statusSummary)
-
+
def testSetAppSessionStateToTimedOut(self):
status = 'TimedOut'
statusSummary = 'things are falling behind'
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setAppSessionState to " + status, "test setAppSessionState to " + status, appSessionId="")
ssn = self.api.setAppSessionState(ar.AppSession.Id, status, statusSummary)
self.assertEqual(ssn.Status, status)
@@ -1555,93 +1565,93 @@ def testSetAppSessionStateToTimedOut(self):
def testSetAppSessionStateToAborted(self):
status = 'Aborted'
statusSummary = 'things are looking bad'
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test setAppSessionState to " + status, "test setAppSessionState to " + status, appSessionId="")
ssn = self.api.setAppSessionState(ar.AppSession.Id, status, statusSummary)
self.assertEqual(ssn.Status, status)
self.assertEqual(ssn.StatusSummary, statusSummary)
-
+
def testSetAppSessionState_StatusException(self):
status = 'PrettyMuchWorkingKindaSorta'
statusSummary = 'tests, what tests'
with self.assertRaises(AppSessionException):
ssn = self.api.setAppSessionState(self.ssn.Id, status, statusSummary)
- def test__deserializeObject__Project(self):
+ def test__deserializeObject__Project(self):
type = 'Project'
dct = { "HrefSamples": "testurl",
"Gibberish": "more Gibberish" }
- new_obj = self.api.__deserializeObject__(dct, type)
+ new_obj = self.api.__deserializeObject__(dct, type)
self.assertEqual(new_obj.HrefSamples, "testurl")
with self.assertRaises(AttributeError):
self.assertEqual(new_obj.Gibberish, "more Gibberish")
-
+
def test__deserializeObject__Sample(self):
type = 'Sample'
dct = { "SampleNumber": "123",
"Gibberish": "more Gibberish" }
- new_obj = self.api.__deserializeObject__(dct, type)
+ new_obj = self.api.__deserializeObject__(dct, type)
self.assertEqual(new_obj.SampleNumber, 123)
with self.assertRaises(AttributeError):
self.assertEqual(new_obj.Gibberish, "more Gibberish")
-
+
def test__deserializeObject__AppResult(self):
type = 'AppResult'
dct = { "Description": "Fuzzy",
"Gibberish": "more Gibberish" }
- new_obj = self.api.__deserializeObject__(dct, type)
+ new_obj = self.api.__deserializeObject__(dct, type)
self.assertEqual(new_obj.Description, "Fuzzy")
with self.assertRaises(AttributeError):
self.assertEqual(new_obj.Gibberish, "more Gibberish")
-
+
def test__deserializeObject__Other(self):
type = 'Other'
dct = { "Description": "Fuzzy",
"Gibberish": "more Gibberish" }
- new_obj = self.api.__deserializeObject__(dct, type)
- self.assertEqual(new_obj, dct)
-
+ new_obj = self.api.__deserializeObject__(dct, type)
+ self.assertEqual(new_obj, dct)
+
class TestAPICoverageMethods(TestCase):
'''
Tests API Coverage object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
-
+
def testGetIntervalCoverage(self):
cov = self.api.getIntervalCoverage(
Id = tconst['bam_file_id'],
Chrom = tconst['bam_cov_chr_name'],
StartPos = tconst['bam_cov_start_coord'],
- EndPos = tconst['bam_cov_end_coord'])
+ EndPos = tconst['bam_cov_end_coord'])
self.assertEqual(cov.Chrom, tconst['bam_cov_chr_name'])
self.assertEqual(cov.StartPos, int(tconst['bam_cov_start_coord']))
- self.assertEqual(cov.EndPos, int(tconst['bam_cov_end_coord']))
+ self.assertEqual(cov.EndPos, int(tconst['bam_cov_end_coord']))
def testGetCoverageMetaInfo(self):
cov_meta = self.api.getCoverageMetaInfo(
Id = tconst['bam_file_id'],
Chrom = tconst['bam_cov_chr_name'])
self.assertTrue(hasattr(cov_meta, 'MaxCoverage'))
-
+
class TestAPIVariantMethods(TestCase):
'''
Tests API Variant object methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
-
- def testFilterVariantSet(self):
+
+ def testFilterVariantSet(self):
vars = self.api.filterVariantSet(
- Id = tconst['vcf_file_id'],
+ Id = tconst['vcf_file_id'],
Chrom = tconst['vcf_chr_name'],
StartPos = tconst['vcf_start_coord'],
- EndPos = tconst['vcf_end_coord'], )
+ EndPos = tconst['vcf_end_coord'], )
self.assertEqual(vars[0].CHROM, tconst['vcf_chr_name'])
-
+
def testFilterVariantWithQp(self):
vars = self.api.filterVariantSet(
- Id = tconst['vcf_file_id'],
+ Id = tconst['vcf_file_id'],
Chrom = tconst['vcf_chr_name'],
StartPos = tconst['vcf_start_coord'],
EndPos = tconst['vcf_end_coord'],
@@ -1649,8 +1659,8 @@ def testFilterVariantWithQp(self):
queryPars = qp({'Limit':1}) )
self.assertEqual(vars[0].CHROM, tconst['vcf_chr_name'])
self.assertEqual(len(vars), 1)
-
- def testFilterVariantReturnVCFString(self):
+
+ def testFilterVariantReturnVCFString(self):
with self.assertRaises(NotImplementedError): # for now...
vars = self.api.filterVariantSet(
Id = tconst['vcf_file_id'],
@@ -1658,38 +1668,38 @@ def testFilterVariantReturnVCFString(self):
StartPos = tconst['vcf_start_coord'],
EndPos = tconst['vcf_end_coord'],
Format = 'vcf')
- #self.assertEqual(type(vars), str)
-
- def testGetVariantMeta(self):
+ #self.assertEqual(type(vars), str)
+
+ def testGetVariantMeta(self):
hdr = self.api.getVariantMetadata(tconst['vcf_file_id'])
self.assertTrue(hasattr(hdr, 'Metadata'))
- def testGetVariantMetaReturnVCFString(self):
+ def testGetVariantMetaReturnVCFString(self):
with self.assertRaises(NotImplementedError): # for now...
- hdr = self.api.getVariantMetadata(tconst['vcf_file_id'], Format='vcf')
+ hdr = self.api.getVariantMetadata(tconst['vcf_file_id'], Format='vcf')
#self.assertEqual(type(hdr), str)
-
+
class TestAPICredentialsMethods(TestCase):
'''
Tests API object credentials methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.profile = 'unit_tests'
self.api = BaseSpaceAPI(profile=self.profile)
-
- def test_setCredentials_AllFromProfile(self):
+
+ def test_setCredentials_AllFromProfile(self):
creds = self.api._setCredentials(clientKey=None, clientSecret=None,
- apiServer=None, apiVersion=None, appSessionId='', accessToken='',
+ apiServer=None, appSessionId='', apiVersion=self.api.version, accessToken='',
profile=self.profile)
- self.assertEqual(creds['clientKey'], self.api.key)
- self.assertEqual('profile' in creds, True)
- self.assertEqual(creds['clientSecret'], self.api.secret)
- self.assertEqual(urljoin(creds['apiServer'], creds['apiVersion']), self.api.apiClient.apiServerAndVersion)
- self.assertEqual(creds['apiVersion'], self.api.version)
- self.assertEqual(creds['appSessionId'], self.api.appSessionId)
+ # self.assertEqual(creds['clientKey'], self.api.key)
+ # self.assertEqual('profile' in creds, True)
+ # self.assertEqual(creds['clientSecret'], self.api.secret)
+ self.assertEqual(urljoin(creds['apiServer'], self.api.version), self.api.apiClient.apiServerAndVersion)
+ # self.assertEqual(creds['apiVersion'], self.api.version)
+ # self.assertEqual(creds['appSessionId'], self.api.appSessionId)
self.assertEqual(creds['accessToken'], self.api.getAccessToken())
- def test_setCredentials_AllFromConstructor(self):
+ def test_setCredentials_AllFromConstructor(self):
creds = self.api._setCredentials(clientKey='test_key', clientSecret='test_secret',
apiServer='https://www.test.server.com', apiVersion='test_version', appSessionId='test_ssn',
accessToken='test_token', profile=self.profile)
@@ -1704,10 +1714,10 @@ def test_setCredentials_AllFromConstructor(self):
def test_setCredentials_MissingConfigCredsException(self):
# Danger: if this test fails unexpectedly, the config file may not be renamed back to the original name
# 1) mv current .basespacepy.cfg, 2) create new with new content,
- # 3) run test, 4) erase new, 5) mv current back
- cfg = os.path.expanduser('~/.basespacepy.cfg')
+ # 3) run test, 4) erase new, 5) mv current back
+ cfg = os.path.expanduser('~/.basespace/unit_tests.cfg')
tmp_cfg = cfg + '.unittesting.donotdelete'
- shutil.move(cfg, tmp_cfg)
+ shutil.move(cfg, tmp_cfg)
new_cfg_content = ("[" + self.profile + "]\n"
"accessToken=test\n"
"appSessionId=test\n")
@@ -1724,65 +1734,66 @@ def test__setCredentials_DefaultsForOptionalArgs(self):
# Danger: if this test fails unexpectedly, the config file may not be renamed back to the original name
# 1) mv current .basespacepy.cfg, 2) create new with new content,
# 3) run test, 4) erase new, 5) mv current back
- cfg = os.path.expanduser('~/.basespacepy.cfg')
+ cfg = os.path.expanduser('~/.basespace/unit_tests.cfg')
tmp_cfg = cfg + '.unittesting.donotdelete'
- shutil.move(cfg, tmp_cfg)
- new_cfg_content = ("[" + self.profile + "]\n"
+ shutil.move(cfg, tmp_cfg)
+ new_cfg_content = ("[DEFAULT]\n"
"clientKey=test\n"
- "clientSecret=test\n"
+ "clientSecret=test\n"
"apiServer=test\n"
- "apiVersion=test\n")
+ "apiVersion=test\n"
+ "accessToken=test\n")
with open(cfg, "w") as f:
- f.write(new_cfg_content)
+ f.write(new_cfg_content)
creds = self.api._setCredentials(clientKey=None, clientSecret=None,
- apiServer=None, apiVersion=None, appSessionId='', accessToken='',
+ apiServer=None, apiVersion=self.api.version, appSessionId='', accessToken='',
profile=self.profile)
self.assertEqual(creds['appSessionId'], '')
- self.assertEqual(creds['accessToken'], '')
+ self.assertEqual(creds['accessToken'], 'test')
os.remove(cfg)
- shutil.move(tmp_cfg, cfg)
+ shutil.move(tmp_cfg, cfg)
- def test__getLocalCredentials(self):
+ def test__getLocalCredentials(self):
creds = self.api._getLocalCredentials(profile='unit_tests')
self.assertEqual('name' in creds, True)
- self.assertEqual('clientKey' in creds, True)
- self.assertEqual('clientSecret' in creds, True)
+ # self.assertEqual('clientKey' in creds, True)
+ # self.assertEqual('clientSecret' in creds, True)
self.assertEqual('apiServer' in creds, True)
- self.assertEqual('apiVersion' in creds, True)
- self.assertEqual('appSessionId' in creds, True)
+ # self.assertEqual('apiVersion' in creds, True)
+ # self.assertEqual('appSessionId' in creds, True)
self.assertEqual('accessToken' in creds, True)
def test__getLocalCredentials_DefaultProfile(self):
creds = self.api._getLocalCredentials(profile=self.profile)
self.assertEqual('name' in creds, True)
- self.assertEqual('clientKey' in creds, True)
- self.assertEqual('clientSecret' in creds, True)
+# self.assertEqual('clientKey' in creds, True)
+# self.assertEqual('clientSecret' in creds, True)
self.assertEqual('apiServer' in creds, True)
- self.assertEqual('apiVersion' in creds, True)
- self.assertEqual('appSessionId' in creds, True)
+ # self.assertEqual('apiVersion' in creds, True)
+# self.assertEqual('appSessionId' in creds, True)
self.assertEqual('accessToken' in creds, True)
- def test__getLocalCredentials_MissingProfile(self):
+ def test__getLocalCredentials_MissingProfile(self):
with self.assertRaises(CredentialsException):
- creds = self.api._getLocalCredentials(profile="SuperCallaFragaListic AppTastic")
+ creds = self.api._getLocalCredentials(profile="SuperCallaFragaListic AppTastic")
class TestAPIGenomeMethods(TestCase):
'''
Tests API object Genome methods
- '''
- def setUp(self):
+ '''
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
def testGetAvailableGenomes(self):
- genomes = self.api.getAvailableGenomes()
+ genomes = self.api.getAvailableGenomes()
#self.assertIsInstance(g[0], GenomeV1.GenomeV1)
self.assertIsInstance(int(genomes[0].Id), int)
-
+
def testGetAvailableGenomesWithQp(self):
genomes = self.api.getAvailableGenomes(qp({'Limit':200}))
genome = next(gen for gen in genomes if gen.Id == tconst['genome_id'])
- self.assertTrue(genome.Id, tconst['genome_id'])
-
+ self.assertTrue(genome.Id, tconst['genome_id'])
+
def testGetGenomeById(self):
g = self.api.getGenomeById(tconst['genome_id'])
self.assertEqual(g.Id, tconst['genome_id'])
@@ -1791,16 +1802,16 @@ class TestAPIUtilityMethods(TestCase):
'''
Tests utility methods of the API object
'''
- def setUp(self):
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
-
+
def test_validateQueryParametersDefault(self):
self.assertEqual(self.api._validateQueryParameters(None), {})
-
+
def test_validateQueryParameters(self):
queryPars = {'Limit':10}
self.assertEqual(self.api._validateQueryParameters( qp(queryPars) ), queryPars)
-
+
def test_validateQueryParametersException(self):
with self.assertRaises(QueryParameterException):
self.api._validateQueryParameters({'Limit':10})
@@ -1809,12 +1820,12 @@ class TestQueryParametersMethods(TestCase):
'''
Tests QueryParameters methods
'''
- def testGetParameterDictAndValidate(self):
+ def testGetParameterDictAndValidate(self):
queryp = qp({'Limit':1}, ['Limit'])
- passed = queryp.getParameterDict()
+ passed = queryp.getParameterDict()
self.assertEqual(passed, {'Limit':1})
self.assertEqual(queryp.validate(), None)
-
+
def testNoDictException(self):
with self.assertRaises(QueryParameterException):
queryp = qp('test')
@@ -1823,14 +1834,14 @@ def testValidateMissingRequiredParameterException(self):
queryp = qp({'Limit':1}, ['I am required'])
with self.assertRaises(UndefinedParameterException):
queryp.validate()
-
+
def testValidateUnknownParameterException(self):
queryp = qp({'Crazy New Parameter':66})
with self.assertRaises(UnknownParameterException):
queryp.validate()
-
+
def testValidateIllegalValueForKnownQpKeyException(self):
- queryp = qp({'SortBy': 'abc'})
+ queryp = qp({'SortBy': 'abc'})
with self.assertRaises(IllegalParameterException):
queryp.validate()
@@ -1852,40 +1863,40 @@ class TestAPIOAuthMethods(TestCase):
'''
Tests API Oauth methods
'''
- def setUp(self):
+ def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
-
+
def testGetAccess_Device(self):
proj = self.api.getProjectById(tconst['project_id'])
resp = self.api.getAccess(proj, accessType='browse')
self.assertTrue('device_code' in resp)
- def testGetAccess_DeviceModelNotSupportedException(self):
+ def testGetAccess_DeviceModelNotSupportedException(self):
with self.assertRaises(ModelNotSupportedException):
- self.api.getAccess("test")
-
+ self.api.getAccess("test")
+
def testGetAccess_Web(self):
proj = self.api.getProjectById(tconst['project_id'])
url = self.api.getAccess(proj, accessType='browse', web=True, redirectURL='http://www.basespacepy.tv', state='working')
self.assertTrue(url.startswith('http'))
-
+
def testGetVerificationCode(self):
resp = self.api.getVerificationCode('browse project ' + tconst['project_id'])
self.assertTrue('device_code' in resp)
-
+
def testGetWebVerificationCode(self):
url = self.api.getWebVerificationCode('browse project ' + tconst['project_id'], redirectURL='http://www.basespacepy.tv')
self.assertTrue(url.startswith('http'))
- self.assertTrue('state=' in url)
-
+ self.assertTrue('state=' in url)
+
def testGetWebVerificationCode_WithStateParam(self):
url = self.api.getWebVerificationCode('browse project ' + tconst['project_id'], redirectURL='http://www.basespacepy.tv', state='working')
self.assertTrue(url.startswith('http'))
self.assertTrue('state=working' in url)
-
+
def testObtainAccessToken_DeviceApp(self):
resp = self.api.getVerificationCode('browse project ' + tconst['project_id'])
- webbrowser.open(resp['verification_with_code_uri'])
+ webbrowser.open(resp['verification_with_code_uri'])
time.sleep(25) # wait for user to accept oauth request
self.assertTrue(isinstance(self.api.obtainAccessToken(resp['device_code']), str))
@@ -1893,15 +1904,15 @@ def testObtainAccessToken_DeviceApp(self):
def testObtainAccessToken_WebApp(self):
with self.assertRaises(Exception):
self.api.obtainAccessToken('123456', grantType='authorization_code', redirect_uri='http://www.basespacepy.tv')
-
- def testObtainAccessToken_WebAppRedirectURIException(self):
+
+ def testObtainAccessToken_WebAppRedirectURIException(self):
with self.assertRaises(OAuthException):
self.api.obtainAccessToken('123456', grantType='authorization_code', redirect_uri=None)
-
+
def testUpdatePrivileges_DeviceApp(self):
resp = self.api.getVerificationCode('browse project ' + tconst['project_id'])
- webbrowser.open(resp['verification_with_code_uri'])
+ webbrowser.open(resp['verification_with_code_uri'])
time.sleep(25) # wait for user to accept oauth request
origToken = self.api.getAccessToken()
self.api.updatePrivileges(resp['device_code'])
@@ -1911,23 +1922,23 @@ def testUpdatePrivileges_DeviceApp(self):
@skip("Not sure how to test, since must parse auth code from redirect url - use django.test assertRedirects()?")
def testUpdatePrivileges_WebApp(self):
with self.assertRaises(Exception):
- self.api.updatePrivileges('123456', grantType='authorization_code', redirect_uri='http://www.basespacepy.tv')
+ self.api.updatePrivileges('123456', grantType='authorization_code', redirect_uri='http://www.basespacepy.tv')
class TestBaseSpaceAPIMethods(TestCase):
'''
Tests BaseSpace API constructor and attributes; all methods tested in other testcases
'''
def setUp(self):
- self.api = BaseSpaceAPI(profile='unit_tests')
-
+ self.api = BaseSpaceAPI(profile='unit_tests')
+
def test__init__(self):
creds = self.api._getLocalCredentials(profile='unit_tests')
- self.assertEqual(creds['appSessionId'], self.api.appSessionId)
- self.assertEqual(creds['clientKey'], self.api.key)
- self.assertEqual(creds['clientSecret'], self.api.secret)
+ # self.assertEqual(creds['appSessionId'], self.api.appSessionId)
+ # self.assertEqual(creds['clientKey'], self.api.key)
+ # self.assertEqual(creds['clientSecret'], self.api.secret)
self.assertEqual(creds['apiServer'], self.api.apiServer)
- self.assertEqual(creds['apiVersion'], self.api.version)
- self.assertEqual(creds['name'], self.api.profile)
+ # self.assertEqual(creds['apiVersion'], self.api.version)
+ # self.assertEqual(creds['name'], self.api.profile)
self.assertEqual(creds['apiServer'].replace('api.',''), self.api.weburl)
class TestBaseAPIMethods(TestCase):
@@ -1935,22 +1946,22 @@ class TestBaseAPIMethods(TestCase):
Tests Base API methods
'''
def setUp(self):
- api = BaseSpaceAPI(profile='unit_tests')
+ api = BaseSpaceAPI(profile='unit_tests')
self.bapi = BaseAPI(api.getAccessToken(), api.apiClient.apiServerAndVersion)
-
+
def test__init__(self):
accessToken = "123"
apiServerAndVersion = "http://api.tv"
timeout = 50
- bapi = BaseAPI(accessToken, apiServerAndVersion, timeout)
+ bapi = BaseAPI(accessToken, apiServerAndVersion, timeout=timeout)
self.assertEqual(bapi.apiClient.apiKey, accessToken)
self.assertEqual(bapi.apiClient.apiServerAndVersion, apiServerAndVersion)
self.assertEqual(bapi.apiClient.timeout, timeout)
def test__singleRequest__(self):
# get current user
- resourcePath = '/users/current'
- method = 'GET'
+ resourcePath = '/users/current'
+ method = 'GET'
queryParams = {}
headerParams = {}
user = self.bapi.__singleRequest__(UserResponse.UserResponse, resourcePath, method, queryParams, headerParams)
@@ -1958,51 +1969,42 @@ def test__singleRequest__(self):
def test__singleRequest__WithPostData(self):
# create a project
- resourcePath = '/projects/'
+ resourcePath = '/projects/'
method = 'POST'
queryParams = {}
headerParams = {}
- postData = { 'Name': tconst['create_project_name'] }
- proj = self.bapi.__singleRequest__(ProjectResponse.ProjectResponse,
+ postData = { 'Name': tconst['create_project_name'] }
+ proj = self.bapi.__singleRequest__(ProjectResponse.ProjectResponse,
resourcePath, method, queryParams, headerParams, postData=postData)
self.assertEqual(proj.Name, tconst['create_project_name'])
-
+
def test__singleRequest__WithForcePost(self):
# initiate a multipart upload -- requires a POST with no post data ('force post')
- api = BaseSpaceAPI(profile='unit_tests')
- proj = api.createProject(tconst['create_project_name'])
- ar = proj.createAppResult(api, "test __singleResult__WithForcePost", "test __singleResult__WithForcePost", appSessionId="")
- resourcePath = '/appresults/{Id}/files'
+ api = BaseSpaceAPI(profile='unit_tests')
+ proj = api.createProject(tconst['create_project_name'])
+ ar = proj.createAppResult(api, "test __singleResult__WithForcePost", "test __singleResult__WithForcePost", appSessionId="")
+ resourcePath = '/appresults/{Id}/files'
method = 'POST'
resourcePath = resourcePath.replace('{Id}', ar.Id)
queryParams = {}
queryParams['name'] = "test file name"
queryParams['directory'] = "test directory"
- queryParams['multipart'] = 'true'
+ queryParams['multipart'] = 'true'
headerParams = {}
- headerParams['Content-Type'] = 'text/plain'
- postData = None
+ headerParams['Content-Type'] = 'text/plain'
+ postData = None
file = self.bapi.__singleRequest__(FileResponse.FileResponse, resourcePath, method,
queryParams, headerParams, postData=postData, forcePost=1)
- self.assertTrue(hasattr(file, 'Id'), 'Successful force post should return file object with Id attribute here')
-
- def test__singleRequest__Verbose(self):
- # get current user
- resourcePath = '/users/current'
- method = 'GET'
- queryParams = {}
- headerParams = {}
- user = self.bapi.__singleRequest__(UserResponse.UserResponse, resourcePath, method, queryParams, headerParams, verbose=True)
- self.assertTrue(hasattr(user, 'Id'))
+ self.assertTrue(hasattr(file, 'Id'), 'Successful force post should return file object with Id attribute here')
@skip("Not sure how to test this, requires no response from api server")
def test__singleRequest__NoneResponseException(self):
pass
-
+
def test__singleRequest__ErrorResponseException(self):
# malformed resoucePath, BadRequest Error and Message in response
- resourcePath = '/users/curren'
- method = 'GET'
+ resourcePath = '/users/curren'
+ method = 'GET'
queryParams = {}
headerParams = {}
with self.assertRaises(ServerResponseException):
@@ -2010,8 +2012,8 @@ def test__singleRequest__ErrorResponseException(self):
def test__singleRequest__UnrecognizedPathResponseException(self):
# malformed resoucePath, Message in response is 'not recognized path' (no error code)
- resourcePath = '/users/current/run'
- method = 'GET'
+ resourcePath = '/users/current/run'
+ method = 'GET'
queryParams = {}
headerParams = {}
with self.assertRaises(ServerResponseException):
@@ -2019,35 +2021,25 @@ def test__singleRequest__UnrecognizedPathResponseException(self):
def test__listRequest__(self):
# get current user
- resourcePath = '/users/current/runs'
- method = 'GET'
+ resourcePath = '/users/current/runs'
+ method = 'GET'
queryParams = {}
headerParams = {}
runs = self.bapi.__listRequest__(Run.Run, resourcePath, method, queryParams, headerParams)
self.assertTrue(isinstance(runs, list))
self.assertTrue(hasattr(runs[0], "Id"))
- def test__listRequest__Verbose(self):
- # get current user
- resourcePath = '/users/current/runs'
- method = 'GET'
- queryParams = {}
- headerParams = {}
- runs = self.bapi.__listRequest__(Run.Run, resourcePath, method, queryParams, headerParams, verbose=True)
- self.assertTrue(isinstance(runs, list))
- self.assertTrue(hasattr(runs[0], "Id"))
-
@skip("Not sure how to test this, requires no response from api server")
def test__listRequest__NoneResponseException(self):
pass
-
+
def test__listRequest__ErrorResponseException(self):
# Unauthorized - use nonsense acccess token
- api = BaseSpaceAPI(profile='unit_tests')
+ api = BaseSpaceAPI(profile='unit_tests')
bapi = BaseAPI(AccessToken="123123123123123123", apiServerAndVersion=api.apiClient.apiServerAndVersion)
- resourcePath = '/users/current/uns'
- method = 'GET'
+ resourcePath = '/users/current/uns'
+ method = 'GET'
queryParams = {}
headerParams = {}
with self.assertRaises(ServerResponseException):
@@ -2055,8 +2047,8 @@ def test__listRequest__ErrorResponseException(self):
def test__listRequest__UnrecognizedPathResponseException(self):
# malformed resoucePath, not recognized path message
- resourcePath = '/users/current/uns'
- method = 'GET'
+ resourcePath = '/users/current/uns'
+ method = 'GET'
queryParams = {}
headerParams = {}
with self.assertRaises(ServerResponseException):
@@ -2067,7 +2059,7 @@ def test__makeCurlRequest__(self):
api = BaseSpaceAPI(profile='unit_tests')
scope = 'browse project ' + tconst['project_id']
postData = [('client_id', api.key), ('scope', scope),('response_type', 'device_code')]
- resp = self.bapi.__makeCurlRequest__(postData, api.apiClient.apiServerAndVersion + deviceURL)
+ resp = self.bapi.__makeCurlRequest__(postData, api.apiClient.apiServerAndVersion + deviceURL)
self.assertTrue('device_code' in resp)
@skip("Not sure how to test this, requires no response from api server")
@@ -2081,19 +2073,19 @@ def test__makeCurlRequest__ServerErrorException(self):
scope = 'browse project ' + tconst['project_id']
postData = [('client_id', 'gibberish'), ('scope', scope),('response_type', 'device_code')]
with self.assertRaises(ServerResponseException):
- self.bapi.__makeCurlRequest__(postData, api.apiClient.apiServerAndVersion + deviceURL)
+ self.bapi.__makeCurlRequest__(postData, api.apiClient.apiServerAndVersion + deviceURL)
def testGetTimeout(self):
self.assertEqual(self.bapi.getTimeout(), 10)
-
+
def testSetTimeout(self):
- self.bapi.setTimeout(20)
+ self.bapi.setTimeout(20)
self.assertEqual(self.bapi.apiClient.timeout, 20)
-
+
def testGetAccessToken(self):
api = BaseSpaceAPI(profile='unit_tests')
self.assertEqual(self.bapi.getAccessToken(), api.apiClient.apiKey)
-
+
def testSetAccessToken(self):
self.bapi.setAccessToken("abc")
self.assertEqual(self.bapi.getAccessToken(), "abc")
@@ -2104,200 +2096,200 @@ class TestAPIClientMethods(TestCase):
'''
def setUp(self):
self.api = BaseSpaceAPI(profile='unit_tests')
- self.apiClient = APIClient(self.api.apiClient.apiKey, self.api.apiClient.apiServerAndVersion)
-
+ self.apiClient = APIClient(self.api.apiClient.apiKey, self.api.apiClient.apiServerAndVersion)
+
def test__init__(self):
accessToken = "abc"
apiServerAndVersion = "http://basesinspaces.tv"
timeout = 20
apiClient = APIClient(AccessToken=accessToken, apiServerAndVersion=apiServerAndVersion, timeout=timeout)
- self.assertEqual(accessToken, apiClient.apiKey)
+ self.assertEqual(accessToken, apiClient.apiKey)
self.assertEqual(apiServerAndVersion, apiClient.apiServerAndVersion)
self.assertEqual(timeout, apiClient.timeout)
def test__forcePostCall__(self):
# initiate a multipart upload -- requires a POST with no post data ('force post')
- # all method params are required for success in this example - resourcePath, headerParams, and postData(queryParams
- proj = self.api.createProject(tconst['create_project_name'])
- ar = proj.createAppResult(self.api, "test__forcePostCall__", "test__forcePostCall__", appSessionId="")
-
+ # all method params are required for success in this example - resourcePath, headerParams, and postData(queryParams
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = proj.createAppResult(self.api, "test__forcePostCall__", "test__forcePostCall__", appSessionId="")
+
resourcePath = '/appresults/{Id}/files'
resourcePath = resourcePath.replace('{Id}', ar.Id)
queryParams = {}
queryParams['name'] = "test file name"
queryParams['directory'] = "test directory"
- queryParams['multipart'] = 'true'
+ queryParams['multipart'] = 'true'
headerParams = {}
headerParams['Content-Type'] = 'text/plain'
# normally added by callAPI()
- headerParams['Authorization'] = 'Bearer ' + self.apiClient.apiKey
+ headerParams['Authorization'] = 'Bearer ' + self.apiClient.apiKey
- jsonResp = self.apiClient.__forcePostCall__(resourcePath=self.apiClient.apiServerAndVersion + resourcePath, postData=queryParams, headers=headerParams)
+ jsonResp = self.apiClient.__forcePostCall__(resourcePath=self.apiClient.apiServerAndVersion + resourcePath, postData=queryParams, headers=headerParams)
dictResp = json.loads(jsonResp)
- self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
- self.assertTrue('Id' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
+ self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
+ self.assertTrue('Id' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
def test__putCall__(self):
# upload a part of a multipart upload (the only PUT call in BaseSpacePy, for now)
testDir = "test__putCall__"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "test__putCall__", "test__putCall__", appSessionId="")
file = self.api.__initiateMultipartFileUpload__(
resourceType = 'appresults',
resourceId = ar.Id,
- fileName = os.path.basename(tconst['file_small_upload']),
+ fileName = os.path.basename(tconst['file_small_upload']),
directory = testDir,
contentType = tconst['file_small_upload_content_type'])
- with open(tconst['file_small_upload']) as fp:
+ with open(tconst['file_small_upload'], "rb") as fp:
out = fp.read()
- md5 = hashlib.md5(out).digest().encode('base64')
-
+ md5 = base64.b64encode(hashlib.md5(out).digest())
+
method = 'PUT'
resourcePath = '/files/{Id}/parts/{partNumber}'
resourcePath = resourcePath.replace('{Id}', file.Id)
- resourcePath = resourcePath.replace('{partNumber}', str(1))
+ resourcePath = resourcePath.replace('{partNumber}', str(1))
headerParams = {'Content-MD5': md5}
- transFile = tconst['file_small_upload']
- putResp = self.apiClient.__putCall__(resourcePath=self.apiClient.apiServerAndVersion + resourcePath, headers=headerParams, transFile=transFile)
- #print "RESPONSE is: " + putResp
+ data = tconst['file_small_upload_contents']
+ putResp = self.apiClient.__putCall__(resourcePath=self.apiClient.apiServerAndVersion + resourcePath, headers=headerParams, data=data)
+ #print("RESPONSE is: " + putResp)
jsonResp = putResp.split()[-1] # normally done in callAPI()
dictResp = json.loads(jsonResp)
- self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
- self.assertTrue('ETag' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
+ self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
+ self.assertTrue('ETag' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
- def testCallAPI_GET(self):
- # get current user uses GET
- resourcePath = '/users/current'
- method = 'GET'
+ def testCallAPI_GET(self):
+ # get current user uses GET
+ resourcePath = '/users/current'
+ method = 'GET'
queryParams = {}
#headerParams = {}
dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=None)#, headerParams=None, forcePost=False)
- self.assertTrue('Response' in dictResp, 'response is: ' + str(dictResp))
- self.assertTrue('Id' in dictResp['Response'])
-
+ self.assertTrue('Response' in dictResp, 'response is: ' + str(dictResp))
+ self.assertTrue('Id' in dictResp['Response'])
+
@skip('There are no GET calls in the BaseSpace API that require headerParams')
def testCallAPI_GETwithHeaderParams(self):
pass
-
+
def testCallAPI_POST(self):
# create a project uses POST
- resourcePath = '/projects/'
+ resourcePath = '/projects/'
method = 'POST'
queryParams = {}
#headerParams = {}
postData = {}
- postData['Name'] = tconst['create_project_name']
+ postData['Name'] = tconst['create_project_name']
dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=postData)#, headerParams=None, forcePost=False)
self.assertTrue('Response' in dictResp)
self.assertTrue('Id' in dictResp['Response'])
def testCallAPI_POSTwithHeaderAndQueryParams(self):
- # single part file upload uses POST with required qp and hdrs
- proj = self.api.createProject(tconst['create_project_name'])
- ar = proj.createAppResult(self.api, "test upload", "test upload", appSessionId="")
+ # single part file upload uses POST with required qp and hdrs
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = proj.createAppResult(self.api, "test upload", "test upload", appSessionId="")
testDir = "testCallAPI_POSTwithHeaderAndQueryParams"
fileName = os.path.basename(tconst['file_small_upload'])
localPath=tconst['file_small_upload']
-
+
method = 'POST'
- resourcePath = '/appresults/{Id}/files'
+ resourcePath = '/appresults/{Id}/files'
resourcePath = resourcePath.replace('{Id}', ar.Id)
queryParams = {}
queryParams['name'] = fileName
- queryParams['directory'] = testDir
+ queryParams['directory'] = testDir
headerParams = {}
- headerParams['Content-Type'] = tconst['file_small_upload_content_type']
+ headerParams['Content-Type'] = tconst['file_small_upload_content_type']
postData = open(localPath).read()
- dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=postData, headerParams=headerParams)#, forcePost=False)
+ dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=postData, headerParams=headerParams)#, forcePost=False)
self.assertTrue('Response' in dictResp)
- self.assertTrue('Id' in dictResp['Response'])
+ self.assertTrue('Id' in dictResp['Response'])
self.assertEqual(dictResp['Response']['Path'], os.path.join(testDir, fileName))
def testCallAPI_ForcePOST(self):
# initiate a multipart upload -- requires a POST with no post data ('force post')
- # all method params are required for success in this example - resourcePath, headerParams, and postData(queryParams
- proj = self.api.createProject(tconst['create_project_name'])
- ar = proj.createAppResult(self.api, "test__forcePostCall__", "test__forcePostCall__", appSessionId="")
-
+ # all method params are required for success in this example - resourcePath, headerParams, and postData(queryParams
+ proj = self.api.createProject(tconst['create_project_name'])
+ ar = proj.createAppResult(self.api, "test__forcePostCall__", "test__forcePostCall__", appSessionId="")
+
method = 'POST'
resourcePath = '/appresults/{Id}/files'
resourcePath = resourcePath.replace('{Id}', ar.Id)
queryParams = {}
queryParams['name'] = "test file name"
queryParams['directory'] = "test directory"
- queryParams['multipart'] = 'true'
+ queryParams['multipart'] = 'true'
headerParams = {}
headerParams['Content-Type'] = 'text/plain'
postData = None
dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=postData, headerParams=headerParams, forcePost=True)
- self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
- self.assertTrue('Id' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
+ self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
+ self.assertTrue('Id' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
def testCallAPI_PUT(self):
# upload a part of a multipart upload (the only PUT call in BaseSpacePy, for now)
testDir = "testCallAPI_PUT"
- proj = self.api.createProject(tconst['create_project_name'])
+ proj = self.api.createProject(tconst['create_project_name'])
ar = proj.createAppResult(self.api, "testCallAPI_PUT", "testCallAPI_PUT", appSessionId="")
file = self.api.__initiateMultipartFileUpload__(
resourceType = 'appresults',
resourceId = ar.Id,
- fileName = os.path.basename(tconst['file_small_upload']),
+ fileName = os.path.basename(tconst['file_small_upload']),
directory = testDir,
contentType = tconst['file_small_upload_content_type'])
- with open(tconst['file_small_upload']) as fp:
+ with open(tconst['file_small_upload'], "rb") as fp:
out = fp.read()
- md5 = hashlib.md5(out).digest().encode('base64')
-
+ md5 = base64.b64encode(hashlib.md5(out).digest())
+
method = 'PUT'
resourcePath = '/files/{Id}/parts/{partNumber}'
resourcePath = resourcePath.replace('{Id}', file.Id)
- resourcePath = resourcePath.replace('{partNumber}', str(1))
+ resourcePath = resourcePath.replace('{partNumber}', str(1))
headerParams = {'Content-MD5': md5}
queryParams = {} # not used for PUT calls
- transFile = tconst['file_small_upload']
- dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=transFile, headerParams=headerParams)
- self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
- self.assertTrue('ETag' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
+ data = tconst['file_small_upload_contents']
+ dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=data, headerParams=headerParams)
+ self.assertTrue('Response' in dictResp, 'Successful force post should return json with Response attribute: ' + str(dictResp))
+ self.assertTrue('ETag' in dictResp['Response'], 'Successful force post should return json with Response with Id attribute: ' + str(dictResp))
- def testCallAPI_DELETE(self):
+ def testCallAPI_DELETE(self):
method = 'DELETE'
- resourcePath = ''
- queryParams = {}
+ resourcePath = ''
+ queryParams = {}
with self.assertRaises(NotImplementedError):
dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=None)
def testCallAPI_UnrecognizedRESTmethodException(self):
method = 'TAKEOVERTHEWORLD'
- resourcePath = ''
- queryParams = {}
+ resourcePath = ''
+ queryParams = {}
with self.assertRaises(RestMethodException):
dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=None)
def testCallAPI_HandleHttpError_ForGET(self):
- # bad access token throws 401 Error and HTTPError exception by urllib2; get current user uses GET
+ # bad access token throws 401 Error and HTTPError exception by urllib2; get current user uses GET
self.apiClient.apiKey = 'badtoken'
- resourcePath = '/users/current'
- method = 'GET'
+ resourcePath = '/users/current'
+ method = 'GET'
queryParams = {}
dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=None)
- self.assertTrue('ResponseStatus' in dictResp, 'response is: ' + str(dictResp))
+ self.assertTrue('ResponseStatus' in dictResp, 'response is: ' + str(dictResp))
self.assertTrue('ErrorCode' in dictResp['ResponseStatus'])
self.assertTrue('Message' in dictResp['ResponseStatus'])
- self.assertEqual(dictResp['ResponseStatus']['Message'], 'Unauthorized')
+ self.assertTrue('Unrecognized access token' in dictResp['ResponseStatus']['Message'])
def testCallAPI_HandleHttpError_ForPOST(self):
- # bad access token throws 401 Error and HTTPError exception by urllib2; create a project uses POST
- self.apiClient.apiKey = 'badtoken'
- resourcePath = '/projects/'
+ # bad access token throws 401 Error and HTTPError exception by urllib2; create a project uses POST
+ self.apiClient.apiKey = 'badtoken'
+ resourcePath = '/projects/'
method = 'POST'
queryParams = {}
postData = {}
- postData['Name'] = tconst['create_project_name']
+ postData['Name'] = tconst['create_project_name']
dictResp = self.apiClient.callAPI(resourcePath, method, queryParams, postData=postData)
- self.assertTrue('ResponseStatus' in dictResp, 'response is: ' + str(dictResp))
+ self.assertTrue('ResponseStatus' in dictResp, 'response is: ' + str(dictResp))
self.assertTrue('ErrorCode' in dictResp['ResponseStatus'])
self.assertTrue('Message' in dictResp['ResponseStatus'])
- self.assertEqual(dictResp['ResponseStatus']['Message'], 'Unauthorized')
+ self.assertTrue('Unrecognized access token' in dictResp['ResponseStatus']['Message'])
@skip('Not sure how to cause json returned from server to be malformed, in order to cause an exception in json parsing')
def testCallAPI_JsonParsingException(self):
@@ -2308,7 +2300,7 @@ def testDeserialize_ClassObjClass_String(self):
objClass = str
out = self.apiClient.deserialize(obj, objClass)
self.assertEqual(out, obj)
-
+
def testDeserialize_ClassObjClass_Integer(self):
obj = 123
objClass = int
@@ -2326,7 +2318,7 @@ def testDeserialize_ClassObjClass_Float(self):
objClass = float
out = self.apiClient.deserialize(obj, objClass)
self.assertEqual(out, obj)
-
+
def testDeserialize_StringObjClass_String(self):
obj = "test"
objClass = 'str'
@@ -2358,7 +2350,7 @@ def testDeserialize_ClassObjClass_Project(self):
self.assertEqual(out.Id, "123")
# not testing passing in an unknown class
-
+
def testDeserialize_StringObjClass_Project(self):
obj = {"Id":"123"}
objClass = "Project"
@@ -2373,23 +2365,23 @@ def testDeserialize_StringObjClass_File(self):
# not testing passing in an unknown class
- def testDeserialize_ClassObjClass_DynamicType(self):
+ def testDeserialize_ClassObjClass_DynamicType(self):
obj = { 'ResponseStatus': 'test',
'Response': { # DynamicType
- # MultiValueAppResultList
- 'Type': 'appresult[]',
+ # MultiValueAppResultList
+ 'Type': 'appresult[]',
'DisplayedCount': 10,
- },
+ },
'Notifications': ''
}
objClass = MultiValuePropertyResponse.MultiValuePropertyResponse
out = self.apiClient.deserialize(obj, objClass)
self.assertEqual(out.Response.DisplayedCount, 10)
- # not testing passing in an unrecognized dynamic type - should warn
-
+ # not testing passing in an unrecognized dynamic type - should warn
+
def testDeserialize_ClassObjClass_List(self):
- obj = { 'CHROM': 'chr3',
+ obj = { 'CHROM': 'chr3',
'ID': ['1', '2', '3'] } # 'list'
objClass = Variant.Variant
out = self.apiClient.deserialize(obj, objClass)
@@ -2399,12 +2391,12 @@ def testDeserialize_ClassObjClass_ListOfDynamicTypes(self):
obj = { 'Items': [ # 'list',
{'Type': 'string', 'Name': 'teststring'}, # PropertyString
{'Type': 'project', 'Name': 'testproject'}, # PropertyProject
- ], }
+ ], }
objClass = PropertyList.PropertyList
out = self.apiClient.deserialize(obj, objClass)
self.assertEqual(out.Items[0].Name, 'teststring')
self.assertEqual(out.Items[1].Name, 'testproject')
-
+
def testDeserialize_ClassObjClass_ListOfLists(self):
obj = { 'Items': [ #'listoflists',
[ {'Key': 'testA1'}, {'Key': 'testA2'}], # PropertyMapKeyValues
@@ -2416,13 +2408,13 @@ def testDeserialize_ClassObjClass_ListOfLists(self):
self.assertEqual(out.Items[1][1].Key, 'testB2')
def testDeserialize_ClassObjClass_Dict(self):
- obj = { 'INFO': 'test' } # dict
+ obj = { 'INFO': 'test' } # dict
objClass = Variant.Variant
out = self.apiClient.deserialize(obj, objClass)
self.assertEqual(out.INFO, 'test')
-
+
def testDeserialize_ClassObjClass_Datetime(self):
- obj = { 'DateCreated': '2013-10-03T19:40:26.0000000' } # datetime
+ obj = { 'DateCreated': '2013-10-03T19:40:26.0000000' } # datetime
objClass = Run.Run
out = self.apiClient.deserialize(obj, objClass)
self.assertEqual(out.DateCreated.year, 2013)
@@ -2440,7 +2432,7 @@ class TestBillingAPIMethods(TestCase):
@skip('Test not written yet')
def test__init__(self):
pass
-
+
class TestQueryParameterPurchasedProductMethods(TestCase):
'''
Tests QueryParameterPurchasedProduct methods
@@ -2450,12 +2442,12 @@ def test__init__(self):
pass
-#if __name__ == '__main__':
+#if __name__ == '__main__':
# main() # unittest.main()
large_file_transfers = TestSuite([
TestLoader().loadTestsFromTestCase( TestAPIFileUploadMethods_LargeFiles ),
TestLoader().loadTestsFromTestCase( TestAPIFileDownloadMethods_LargeFiles ),
- TestLoader().loadTestsFromTestCase( TestMultipartFileTransferMethods ), ])
+ TestLoader().loadTestsFromTestCase( TestMultipartFileTransferMethods ), ])
small_file_transfers = TestSuite([
TestLoader().loadTestsFromTestCase(TestFileDownloadMethods),
@@ -2468,7 +2460,7 @@ def test__init__(self):
TestLoader().loadTestsFromTestCase(TestUserMethods),
TestLoader().loadTestsFromTestCase(TestAPIUserMethods),
TestLoader().loadTestsFromTestCase(TestFileMethods),
- TestLoader().loadTestsFromTestCase(TestAPIFileMethods), ])
+ TestLoader().loadTestsFromTestCase(TestAPIFileMethods), ])
samples_appresults_projects = TestSuite([
TestLoader().loadTestsFromTestCase(TestSampleMethods),
@@ -2513,20 +2505,22 @@ def test__init__(self):
if(len(sys.argv) == 1):
# to test all test cases:
- tests.extend([
- small_file_transfers,
- runs_users_files,
+ tests.extend([
+ small_file_transfers,
+ runs_users_files,
samples_appresults_projects,
- appsessions,
+ appsessions,
cred_genome_util_lists,
- cov_variant,
+ cov_variant,
basespaceapi_baseapi_apiclient,
billing_qppp,
])
#tests.append(oauth) # these tests will open a web browser and clicking 'Accept' (also requires BaseSpace login)
tests.append(large_file_transfers) # these tests may take tens of minutes to complete
else:
- # to test individual test cases:
+ # to test individual test cases:
for t in sys.argv[1:]:
tests.append( TestLoader().loadTestsFromTestCase( eval(t) ) )
- TextTestRunner(verbosity=2).run( TestSuite(tests) )
+ ret = TextTestRunner(verbosity=2).run( TestSuite(tests) )
+ exitcode = not ret.wasSuccessful()
+ sys.exit(exitcode)