Package: cloud-files
CloudFiles: Fast access to cloud storage and local FS.fromcloudfilesimportCloudFiles,CloudFile,dlresults=dl(["gs://bucket/file1","gs://bucket2/file2",...])# shorthandcf=CloudFiles('gs://bucket',progress=True)# s3://, https://, and file:// also supportedresults=cf.get(['file1','file2','file3',...,'fileN'])# threadedresults=cf.get(paths,parallel=2)# threaded and two processesfile1=cf['file1']part=cf['file1',0:30]# first 30 bytes of file1cf.put('filename',content)cf.put_json('filename',content)cf.puts([{'path':'filename','content':content,},...])# automatically threadedcf.puts(content,parallel=2)# threaded + two processescf.puts(content,storage_class="NEARLINE")# apply vendor-specific storage classcf.put_jsons(...)# same as putscf['filename']=contentforfnameincf.list(prefix='abc123'):print(fname)list(cf)# same as list(cf.list())cf.delete('filename')delcf['filename']cf.delete(['filename_1','filename_2',...])# threadedcf.delete(paths,parallel=2)# threaded + two processesboolean=cf.exists('filename')results=cf.exists(['filename_1',...])# threaded# for single filescf=CloudFile("gs://bucket/file1")info=cf.head()binary=cf.get()cf.put(binary)cf[:30]# get first 30 bytes of fileCloudFiles was developed to access files from object storage without ever touching disk. The goal was to reliably and rapidly access a petabyte of image data broken down into tens to hundreds of millions of files being accessed in parallel across thousands of cores. CloudFiles has been used to processes dozens of images, many of which were in the hundreds of terabyte range. It has reliably read and written tens of billions of files to date.HighlightsFast file access with transparent threading and optionally multi-process access w/ local file locking.Google Cloud Storage, Amazon S3, local filesystems, and arbitrary web servers making hybrid or multi-cloud easy.Robust to flaky network connections. Uses exponential random window retries to avoid network collisions on a large cluster. Validates md5 for gcs and s3.gzip, brotli, bz2, zstd, and xz compression.Supports HTTP Range reads.Supports green threads, which are important for achieving maximum performance on virtualized servers.High efficiency transfers that avoid compression/decompression cycles.High speed gzip decompression using libdeflate (compared with zlib).Bundled CLI tool.Accepts iterator and generator input.Resumable bulk transfers.Supports composite parallel upload for GCS and multi-part upload for AWS S3.Supports s3 and GCS internal copies to avoid unnecessary data movement.Installationpipinstallcloud-files
pipinstallcloud-files[test]# to enable testing with pytestIf you run into trouble installing dependenies, make sure you're using at least Python3.6 and you have updated pip. On Linux, some dependencies require manylinux2010 or manylinux2014 binaries which earlier versions of pip do not search for. MacOS, Linux, and Windows are supported platforms.CredentialsYou may wish to install credentials under~/.cloudvolume/secrets. CloudFiles is descended from CloudVolume, and for now we'll leave the same configuration structure in place.You need credentials only for the services you'll use. The local filesystem doesn't need any. Google Storage (setup instructions here) will attempt to use default account credentials if no service account is provided.If neither of those two conditions apply, you need a service account credential.google-secret.jsonis a service account credential for Google Storage,aws-secret.jsonis a service account for S3, etc. You can support multiple projects at once by prefixing the bucket you are planning to access to the credential filename.google-secret.jsonwill be your defaut service account, but if you also want to also access bucket ABC, you can provideABC-google-secret.jsonand you'll have simultaneous access to your ordinary buckets and ABC. The secondary credentials are accessed on the basis of the bucket name, not the project name.mkdir-p~/.cloudvolume/secrets/
mvaws-secret.json~/.cloudvolume/secrets/# needed for Amazonmvgoogle-secret.json~/.cloudvolume/secrets/# needed for Googlemvmatrix-secret.json~/.cloudvolume/secrets/# needed for Matrixaws-secret.jsonandmatrix-secret.jsonCreate anIAM user service accountthat can read, write, and delete objects from at least one bucket.{"AWS_ACCESS_KEY_ID":"$MY_AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY":"$MY_SECRET_ACCESS_TOKEN","AWS_DEFAULT_REGION":"$MY_AWS_REGION"// defaults to us-east-1}google-secret.jsonYou can create thegoogle-secret.jsonfilehere. You don't need to manually fill in JSON by hand, the below example is provided to show you what the end result should look like. You should be able to read, write, and delete objects from at least one bucket.{"type":"service_account","project_id":"$YOUR_GOOGLE_PROJECT_ID","private_key_id":"...","private_key":"...","client_email":"...","client_id":"...","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":""}API DocumentationNote that the "Cloud Costs" mentioned below are current as of June 2020 and are subject to change. As of this writing, S3 and Google use identical cost structures for these operations.Constructor# import gevent.monkey # uncomment when using green threads# gevent.monkey.patch_all(thread=False)fromcloudfilesimportCloudFilescf=CloudFiles(cloudpath,progress=False,green=None,secrets=None,num_threads=20,use_https=False,endpoint=None,request_payer=None,composite_upload_threshold=int(1e8))# cloudpath examples:cf=CloudFiles('gs://bucket/')# google cloud storagecf=CloudFiles('s3://bucket/')# Amazon S3cf=CloudFiles('s3://https://s3emulator.com/coolguy/')# alternate s3 endpointcf=CloudFiles('file:///home/coolguy/')# local filesystemcf=CloudFiles('mem:///home/coolguy/')# in memorycf=CloudFiles('https://website.com/coolguy/')# arbitrary web servercloudpath: The path to the bucket you are accessing. The path is formatted as$PROTOCOL://BUCKET/PATH. Files will then be accessed relative to the path. The protocols supported aregs(GCS),s3(AWS S3),file(local FS),mem(RAM), andhttp/https.progress: Whether to display a progress bar when processing multiple items simultaneously.green: Use green threads. For this to work properly, you must uncomment the top two lines. Green threads are used automatically upon monkey patching if green is None.secrets: Provide secrets dynamically rather than fetching from the credentials directory$HOME/.cloudvolume/secrets.num_threads: Number of simultaneous requests to make. Usually 20 per core is pretty close to optimal unless file sizes are extreme.use_https:gs://ands3://require credentials to access their files. However, each has a read-only https endpoint that sometimes requires no credentials. If True, automatically convertgs://tohttps://storage.googleapis.com/ands3://tohttps://s3.amazonaws.com/.endpoint: (s3 only) provide an alternate endpoint than the official Amazon servers. This is useful for accessing the various S3 emulators offered by on-premises deployments of object storage.request_payer: Specify the account that should be charged for requests towards the bucket, rather than the bucket owner.gs://:request_payercan be any Google Cloud project id. Please refer to the documentation formore information.s3://:request_payermust be"requester". The AWS account associated with the AWS_ACCESS_KEY_ID will be charged. Please refer to the documentation formore informationThe advantage of usingmem://versus adicthas both the advantage of using identical interfaces in your code and it will use compression  automatically.get / get_json# Let 'filename' be the file b'hello world'binary=cf.get('filename')binary=cf['filename']>>b'hello world'compressed_binary=cf.get('filename',raw=True)binaries=cf.get(['filename1','filename2'])>>[{'path':'filename1','content':b'...','byte_range':(None,None),'error':None},{'path':'filename2','content':b'...','byte_range':(None,None),'error':None}]# total provides info for progress bar when using generators.binaries=cf.get(generator(),total=N)binary=cf.get({'path':'filename','start':0,'end':5})# fetches 5 bytesbinary=cf['filename',0:5]# only fetches 5 bytesbinary=cf['filename'][0:5]# same result, fetches 11 bytes>>b'hello'# represents byte range 0-4 inclusive of filenamebinaries=cf[:100]# download the first 100 filesgetsupports several different styles of input. The simplest takes a scalar filename and returns the contents of that file. However, you can also specify lists of filenames, a byte range request, and lists of byte range requests. You can provide a generator or iterator as input as well. Order is not guaranteed.When more than one file is provided at once, the download will be threaded using preemptive or cooperative (green) threads depending on thegreensetting. Ifprogressis set to true, a progress bar will be displayed that counts down the number of files to download.get_jsonis the same as get but it will parse the returned binary as JSON data encoded as utf8 and returns a dictionary. Order is guaranteed.Cloud Cost: Usually about $0.40 per million requests.put / puts / put_json / put_jsonscf.put('filename',b'content')cf['filename']=b'content'cf.put_json('digits',[1,2,3,4,5])cf.puts([{'path':'filename','content':b'...','content_type':'application/octet-stream','compress':'gzip','compression_level':6,# parameter for gzip or brotli compressor'cache_control':'no-cache',}])cf.puts([(path,content),(path,content)],compression='gzip')cf.put_jsons(...)# Definition of put, put_json is identicaldefput(self,path,content,content_type=None,compress=None,compression_level=None,cache_control=None,raw=False)# Definition of puts, put_jsons is identicaldefputs(self,files,content_type=None,compress=None,compression_level=None,cache_control=None,total=None,raw=False)The PUT operation is the most complex operation because it's so configurable. Sometimes you want one file, sometimes many. Sometimes you want to configure each file individually, sometimes you want to standardize a bulk upload. Sometimes it's binary data, but oftentimes it's JSON. We provide a simpler interface for uploading a single fileputandput_json(singular) versus the interface for uploading possibly many filesputsandput_jsons(plural).In order to upload many files at once (which is much faster due to threading), you need to minimally provide thepathandcontentfor each file. This can be done either as a dict containing those fields or as a tuple(path, content). If dicts are used, the fields (if present) specified in the dict take precedence over the parameters of the function. You can mix tuples with dicts. The input to puts can be a scalar (a single dict or tuple) or an iterable such as a list, iterator, or generator.Cloud Cost: Usually about $5 per million files.deletecf.delete('filename')cf.delete(['file1','file2',...])delcf['filename']This will issue a delete request for each file specified in a threaded fashion.Cloud Cost: Usually free.existscf.exists('filename')>>True# or Falsecf.exists(['file1','file2',...])>>{'file1':True,'file2':False,...}Scalar input results in a simple boolean output while iterable input returns a dictionary of input paths mapped to whether they exist. In iterable mode, a progress bar may be displayed and threading is utilized to improve performance.Cloud Cost: Usually about $0.40 per million requests.sizecf.size('filename')>>>1337# size in bytescf.size(['file1','nonexistent','empty_file',...])>>>{'file1':392,'nonexistent':None,'empty_file':0,...}The output is the size of each file as it is stored in bytes. If the file doesn't exist,Noneis returned. Scalar input results in a simple boolean output while iterable input returns a dictionary of input paths mapped to whether they exist. In iterable mode, a progress bar may be displayed and threading is utilized to improve performance.Cloud Cost: Usually about $0.40 per million requests.listcf.list()# returns generatorlist(cf)# same as list(cf.list())cf.list(prefix="abc")cf.list(prefix="abc",flat=True)Recall that in object storage, directories do not really exist and file paths are really a key-value mapping. Thelistoperator will list everything under thecloudpathgiven in the constructor. Theprefixoperator allows you to efficiently filter some of the results. Ifflatis specified, the results will be filtered to return only a single "level" of the "directory" even though directories are fake. The entire set of all subdirectories will still need to be fetched.Cloud Cost: Usually about $5 per million requests, but each request might list 1000 files. The list operation will continuously issue list requests lazily as needed.transfer_to / transfer_fromcff=CloudFiles('file:///source_location')cfg=CloudFiles('gs://dest_location')# Transfer all files from local filesys to google cloud storagecfg.transfer_from(cff,block_size=64)# in blocks of 64 filescff.transfer_to(cfg,block_size=64)cff.transfer_to(cfg,block_size=64,reencode='br')# change encoding to brotlicfg[:]=cff# default block size 64Transfer semantics provide a simple way to perform bulk file transfers. Use theblock_sizeparameter to adjust the number of files handled in a given pass. This can be important for preventing memory blow-up and reducing latency between batches.gs to gs and s3 to s3 transfers will occur within the cloud without looping through the executing client provided no reencoding is specified.resumable transferfromcloudfilesimportResumableTransfer# .db b/c this is a sqlite database# that will be automatically createdrt=ResumableTransfer("NAME_OF_JOB.db")# init should only be called oncert.init("file://source_location","gs://dest_location")# This part can be interrupted and resumedrt.execute("NAME_OF_JOB.db")# If multiple transfer clients, the lease_msec# parameter must be specified to prevent conflicts.rt.execute("NAME_OF_JOB.db",lease_msec=30000)rt.close()# deletes NAME_OF_JOB.dbThis is esentially a more durable version ofcp. The transfer works by first loading a sqlite database with filenames, a "done" flag, and a lease time. Then clients can attach to the database and execute the transfer in batches. When multiple clients are used, a lease time must be set so that the database does not return the same set of files to each client (and is robust).This transfer type can also be accessed via the CLI.cloudfilesxferinitSOURCEDEST--dbNAME_OF_JOB.db
cloudfilesxferexecuteNAME_OF_JOB.db# deletes db when donecomposite upload (Google Cloud Storage)If a file is larger than 100MB (default), CloudFiles will split the file into 100MB parts and upload them as individual part files using the STANDARD storage class to minimize deletion costs. Once uploaded, the part files will be recursively merged in a tree 32 files at a time. After each merge, the part files will be deleted. The final file will have the default storage class for the bucket.If an upload is interrupted, the part files will remain and must be cleaned up. You can provide an open for binary reading file handle instead of a bytes object so that large files can be uploaded without overwhelming RAM. You can also adjust the composite threshold usingCloudFiles(..., composite_upload_threshold=int(2e8))to for example, raise the threshold to 200MB.mutli-part upload (S3)If a file is larger than 100MB (default), the S3 service will use multi-part upload. ou can provide an open for binary reading file handle instead of a bytes object so that large files can be uploaded without overwhelming RAM. You can also adjust the composite threshold usingCloudFiles(..., composite_upload_threshold=int(2e8))to for example, raise the threshold to 200MB.Unfinished upload parts remain on S3 (and cost money) unless you use a bucket lifecycle rule to remove them automatically.https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.htmltranscodefromcloudfiles.compressionimporttranscodefiles=cf.get(...)forfileintranscode(files,'gzip'):file['content']# gzipped file content regardless of sourcetranscode(files,encoding='gzip',# any cf compatible compression schemein_place=False,# modify the files in-place to save memoryprogress=True# progress bar)Sometimes we want to change the encoding type of a set of arbitrary files (often when moving them around to another storage system).transcodewill take the output ofgetand transcode the resultant files into a new format.transcoderespects therawattribute which indicates that the contents are already compressed and will decompress them first before recompressing. If the input data are already compressed to the correct output encoding, it will simply pass it through without going through a decompression/recompression cycle.transcodereturns a generator so that the transcoding can be done in a streaming manner.Network RobustnessCloudFiles protects itself from network issues in several ways.First, it uses a connection pool to avoid needing to reestablish connections or exhausting the number of available sockets.Second, it uses an exponential random window backoff to retry failed connections and requests. The exponential backoff allows increasing breathing room for an overloaded server and the random window decorrelates independent attempts by a cluster. If the backoff was not growing, the retry attempts by a large cluster would be too rapid fire or inefficiently slow. If the attempts were not decorrellated, then regardless of the backoff, the servers will often all try again around the same time. We backoff seven times starting from 0.5 seconds to 60 seconds, doubling the random window each time.Third, for Google Cloud Storage (GCS) and S3 endpoints, we compute the md5 digest both sending and receiving to ensure data corruption did not occur in transit and that the server sent the full response. We cannot validate the digest for partial ("Range") reads. Forcomposite objects(GCS) we can check thecrc32ccheck-sum which catches transmission errors but not tampering (though MD5 isn't secure at all anymore). We are unable to perform validation formulti-part uploads(S3). Using custom encryption keys may also create validation problems.Local File LockingWhen accessing local files (file://), CloudFiles can use thefastenerslibrary to perform locking so multi-process IO can be performed safely. These options have no effect on remote file access such asgs://ors3://.Lock files are by default stored in$CLOUD_FILES_DIR/locks(usually~/.cloudfiles/locks).Locking is enabled if:CloudFiles(..., locking=True)CloudFiles(..., locking=None)and a locking directory is set either viaCloudFiles(..., lock_dir=...)or via the environment variableCLOUD_FILES_LOCK_DIR.Locking is always disabled iflocking=False. This can be advantageous for performance reasons but may require careful design of access patterns to avoid reading a file that is being written to.You can check the current assigned locks directory:cf.lock_dirYou can clear the lock dir of all locks with:cf.clear_locks()CloudFiles CLI Tool# list cloud and local directoriescloudfileslsgs://bucket-folder/# parallel file transfer, no decompressioncloudfiles-p2cp--progress-rs3://bkt/gs://bkt2/# change compression type to brotlicloudfilescp-cbrs3://bkt/file.txtgs://bkt2/# decompresscloudfilescp-cnones3://bkt/file.txtgs://bkt2/# pass from stdin (use "-" for source argument)findsome_dir|cloudfilescp-s3://bkt/# resumable transferscloudfilesxferinitSRCDEST--dbJOBNAME.db
cloudfilesxferexecuteJOBNAME.db--progress# can quit and resume# Get human readable file sizes from anywherecloudfilesdu-shc./tmpgs://bkt/dirs3://bkt/dir# remove filescloudfilesrm./tmpgs://bkt/dir/files3://bkt/dir/file# cat across services, -r for range readscloudfilescat./tmpgs://bkt/dir/files3://bkt/dir/file# verify a transfer was successful by comparing bytes and hashescloudfilesverify./my-datags://bucket/my-data/cpPros and ConsFor the cp command, the bundled CLI tool has a number of advantages vs.gsutilwhen it comes to transfers.No decompression of file transfers (unless you want to).Can shift compression format.Easily control the number of parallel processes.Green threads make core utilization more efficient.Optionally uses libdeflate for faster gzip decompression.It also has some disadvantages:Doesn't support all commands.File suffixes may be added to signify compression type on the local filesystem (e.g..gz,.br, or.zstd).cloudfiles lswill list them without the extension and they will be converted intoContent-Encodingon cloud storage.lsGenerative ExpressionsFor thelscommand, we support (via the-eflag) simple generative expressions that enable querying multiple prefixes at once. A generative expression is denoted[chars]wherec,h,a,r, &swill be inserted individually into the position where the expression appears. Multiple expressions are allowed and produce a cartesian product of resulting strings. This functionality is very limited at the moment but we intend to improve it.cloudfilesls-e"gs://bucket/prefix[ab]"# equivalent to:# cloudfiles ls gs://bucket/prefixa# cloudfiles ls gs://bucket/prefixbaliasfor Alternative S3 EndpointsYou can set your own protocols for S3 compatible endpoints by creating dynamic or persistent aliases. CloudFiles comes with two official s3 endpoints that are important for the Seung Lab,matrix://andtigerdata://which point to Princeton S3 endpoints. Official aliases can't be overridden.To create a dynamic alias, you can usecloudfiles.paths.add_aliaswhich will only affect the current process. To create a persistent alias that resides in~/.cloudfiles/aliases.json, you can use the CLI.cloudfilesaliasaddexamples3://https://example.com/# example://cloudfilesaliasls# list all aliasescloudfilesaliasrmexample# remove example://The alias file is only accessed (and cached) if CloudFiles encounters an unknown protocol. If you stick to default protocols and use the syntaxs3://https://example.com/for alternative endpoints, you can still use CloudFiles in environments without filesystem access.CreditsCloudFiles is derived from theCloudVolume.Storagesystem.Storage was initially created by William Silversmith and Ignacio Tartavull. It was maintained and improved by William Silversmith and includes improvements by Nico Kemnitz (extremely fast exists), Ben Falk (brotli), and Ran Lu (local file locking). Manuel Castro added the ability to chose cloud storage class. Thanks to the anonymous author fromhttps://teppen.io/for their s3 etag validation code.