"""
s3 storage operations
"""
from io import BytesIO
import boto3
from botocore.exceptions import ClientError

sessions = {}   # a dictionary of stored S3 sessions for reuse (in case they are expensive to establish)


def get_s3_object(bucket, aws_access_key_id, aws_secret_access_key, location, database, blob_hash):
    # create an S3 object or return the existing copy
    cred = (aws_access_key_id, aws_secret_access_key)
    if cred not in sessions:
        session = boto3.Session(aws_access_key_id=aws_access_key_id,
                                aws_secret_access_key=aws_secret_access_key)
        sessions[cred] = session.resource('s3')
    remote_path = '/'.join((location.lstrip('/'), database, blob_hash))
    return sessions[cred].Object(bucket, remote_path)


def exists(**spec):
    try:
        get_s3_object(**spec).load()
    except ClientError as e:
        if e.response['Error']['Code'] != "404":
            raise
        return False
    return True


def put(blob, **spec):
    if not exists(**spec):
        get_s3_object(**spec).upload_fileobj(BytesIO(blob))


def get(**spec):
    obj = BytesIO()
    get_s3_object(**spec).download_fileobj(obj)
    return obj.getvalue()


def delete(**spec):
    r = get_s3_object(**spec).delete()
    return r['ResponseMetadata']['HTTPStatusCode'] == 204

@@ -1,18 +1,14 @@
 %22%22%22%0A
-s3 storage
+AWS S3
  ope
@@ -138,12 +138,12 @@
  of 
-stor
+reus
 ed S
@@ -156,81 +156,128 @@
 ions
- for reuse (in case they are expensive to establish)%0A%0A%0Adef get_s3_object(
+%0A%0A%0Aclass S3:%0A    %22%22%22%0A    An S3 instance manipulates one specific object stored in AWS S3%0A    %22%22%22%0A    def __init__(self, 
 buck
@@ -362,58 +362,8 @@
     
-# create an S3 object or return the existing copy%0A
     
@@ -408,24 +408,28 @@
 access_key)%0A
+    
     if cred 
@@ -445,16 +445,49 @@
 ssions:%0A
+            # cache sessions%0A    
         
@@ -547,16 +547,20 @@
 key_id,%0A
+    
         
@@ -632,24 +632,28 @@
 ey)%0A        
+    
 sessions%5Bcre
@@ -680,16 +680,20 @@
 e('s3')%0A
+    
     remo
@@ -760,22 +760,33 @@
 h))%0A    
-return
+    self.object =
  session
@@ -826,67 +826,102 @@
 h)%0A%0A
-%0Adef exists(**spec):%0A    try:%0A        get_s3_object(**spec)
+    def __bool__(self):%0A        # True if object is found%0A        try:%0A            self.object
 .loa
@@ -928,16 +928,20 @@
 d()%0A    
+ 
+   
 except C
@@ -957,16 +957,20 @@
 r as e:%0A
+    
         
@@ -1026,14 +1026,22 @@
     
+ 
+   
 raise%0A
+    
     
@@ -1057,16 +1057,38 @@
 n False%0A
+        else:%0A        
     retu
@@ -1100,17 +1100,20 @@
 ue%0A%0A
-%0A
+    
 def put(
 blob
@@ -1112,79 +1112,65 @@
 put(
-blob, **spec
+self, blob
 ):%0A    
+ 
+   
 if not 
-exists(**spec):%0A        get_s3_object(**spec)
+self:%0A            self.object
 .upl
@@ -1201,17 +1201,20 @@
 ))%0A%0A
-%0A
+    
 def get(
 **sp
@@ -1209,25 +1209,27 @@
 def get(
-**spec):%0A
+self):%0A    
     obj 
@@ -1244,37 +1244,24 @@
 O()%0A    
+  
-get_s3_object(**spec)
+  self
 .downloa
@@ -1275,24 +1275,28 @@
 bj(obj)%0A    
+ 
+   
 return obj.g
@@ -1306,17 +1306,20 @@
 alue()%0A%0A
-%0A
+    
 def dele
@@ -1325,46 +1325,38 @@
 ete(
-**spec
+self
 ):%0A    
+  
+  
 r = 
-get_s3_object(**spec)
+self.object
 .del
@@ -1361,16 +1361,20 @@
 elete()%0A
+    
     retu

add class S3 in s3.py to manipulate a single S3 address