Commits

Dhananjay Nene  committed 4a5e07f

Initial commit for version 0.1.0 containing a minimal set of capabilities.

  • Participants

Comments (0)

Files changed (7)

+pyterrastore - Python Client for Terrastore
+
+Terrastore is a modern document store which provides advanced scalability and elasticity features without sacrificing consistency. It is located at http://code.google.com/p/terrastore/
+
+pyterrastore is licensed under the Apache Software License Version 2.0 as detailed in the file license.txt
+
+#############################################################################
+#    Copyright 2010 Dhananjay Nene 
+#    
+#    Licensed under the Apache License, Version 2.0 (the "License"); 
+#    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.
+############################################################################# 
+

File src/pyterrastore/__init__.py

+#############################################################################
+#    Copyright 2010 Dhananjay Nene 
+#    
+#    Licensed under the Apache License, Version 2.0 (the "License"); 
+#    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.
+############################################################################# 

File src/pyterrastore/terrastore.py

+#############################################################################
+#    Copyright 2010 Dhananjay Nene 
+#    
+#    Licensed under the Apache License, Version 2.0 (the "License"); 
+#    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 httplib
+import socket
+import json
+
+class TerrastoreException(Exception):
+    "An exception class to encapsulate HTTP Errors"
+    def __init__(self,code,reason,method=None,url=None,path=None,
+                    content=None,headers=None):
+        self.code = code
+        self.reason = reason
+        self.method = method
+        self.url = url
+        self.path = path
+        self.content = content,
+        self.headers = headers
+    def __str__(self):
+        return self.__repr__()
+    def __repr__(self):
+        return 'TerrastoreExceptions(%s:%s:%s:%s/%s %s)' % \
+            (self.code,self.reason,self.method,self.url,
+                self.path,self.headers)
+
+def http_success(status):
+    return 200 <= status < 300
+
+def http_not_found(status):
+    return status == 404
+
+def expect_code(code):
+    "A predicate constructor for defining acceptable code value"
+    def inner(status): 
+        "The actual predicate to do the checking"
+        return status == code
+    return inner
+        
+def expect_codes(*codes):
+    "A predicate constructor for defining acceptable code value"
+    def inner(status): 
+        "The actual predicate to do the checking"
+        return status in codes
+    return inner
+        
+# This method is not to be treated as a public API (which is why it starts
+# with an _. Future implementations may split the method or convert it into a
+# class with connection getting reused across requests.
+
+def _invoke_http(urls,method,path,content=None,
+                    headers={"Content-type": "application/json"},
+                    success_predicate = http_success,
+                    ignore_predicate = None ):
+    "The generic http request handler"
+    
+    # Incoming urls can either be a string or a sequence of urls
+    for url in (urls,) if isinstance(urls,basestring) else urls :
+        conn = httplib.HTTPConnection(url)
+        try :
+            conn.request(method,path,content,headers)
+            response = conn.getresponse()
+            # check if the http response code is as expected
+            if success_predicate(response.status) :
+                data = response.read()
+                conn.close()
+                return data
+            else :
+                if ignore_predicate and ignore_predicate(response.status) :
+                    ## ignore
+                    return None
+                raise TerrastoreException(response.status,response.reason,
+                                method,url,path,content,headers)
+        except Exception as e :
+            if not isinstance(e,(socket.timeout,socket.error)) :
+                raise e
+            
+    # This is not right. Should create a different exception. 
+    # Just being lazy
+    raise TerrastoreException(999,'Could not connect to any server')
+        
+class TerrastoreServer(object):
+    def __init__(self,urls):
+        self.urls = (urls,) if isinstance(urls,basestring) else urls
+    def create_bucket(self,bucket_name):
+        "Create a new bucket"
+        _invoke_http(self.urls,'PUT','/%s' % bucket_name, 
+                           success_predicate = expect_code(204))
+        return TerrastoreBucket(self,bucket_name)
+    def get_bucket_names(self):
+        "Get all bucket names"
+        bucket_names_json = _invoke_http(self.urls,'GET','/',success_predicate = expect_code(200))
+        bucket_names = json.loads(bucket_names_json)
+        return bucket_names
+    def get_buckets(self):
+        return tuple(TerrastoreBucket(self,bucket_name) for bucket_name in self.get_bucket_names())
+    def remove_bucket(self,bucket_name):
+        "Remove an existing bucket"
+        return _invoke_http(self.urls,'DELETE','/%s' % bucket_name,
+                           success_predicate = expect_code(204))
+    def __str__(self):
+        return self.__repr__()
+    def __repr__(self):
+        return 'TerrastoreServer(%s)' % self.urls
+        
+
+class TerrastoreBucket(object):
+    def __init__(self,server,name):
+        self.server = server
+        self.name = name
+    def get_contents(self,max_elements=0):
+        "Get all the contents in a bucket in a native json format"
+        ret = _invoke_http(self.server.urls,'GET','/%s%s' % (self.name,
+                '' if max_elements == 0 else '?limit=%d' % max_elements),
+                success_predicate = expect_code(200))
+        return ret
+
+    def get_documents(self,max_elements=0):
+        "Get all the documents in a bucket"
+        return json.loads(self.get_contents(max_elements))
+        
+    def put(self,key,content):
+        """Insert a new or update an existing document json string 
+        in the bucket for the given key"""
+        return _invoke_http(self.server.urls,'PUT','/%s/%s' % 
+                           (self.name,key),content, 
+                           success_predicate = expect_code(204))
+    def put_document(self,key,content):
+        """Insert a new or update an existing document 
+        in the bucket for the given key"""
+        docstr = json.dumps(content)
+        return _invoke_http(self.server.urls,'PUT','/%s/%s' % 
+                           (self.name,key),docstr, 
+                           success_predicate = expect_code(204))
+    def get(self,key,default = None):
+        "Fetch a document in the bucket with the given key"
+        docstr = _invoke_http(self.server.urls,'GET','/%s/%s' % (self.name,key),
+                           success_predicate = expect_code(200),
+                           ignore_predicate = http_not_found)
+        if not docstr : docstr = default 
+        return docstr
+    def get_document(self,key,object_hook = None, default = None):
+        docstr = self.get(key,None)
+        if docstr :
+            doc = json.loads(docstr,object_hook=object_hook)
+            return doc
+        else :
+            return default
+    def remove(self,key):
+        "Remove a document in the bucket with the given key"
+        return _invoke_http(self.server.urls,'DELETE','/%s/%s' % 
+                           (self.name,key),success_predicate = expect_code(204))
+
+    def __str__(self):
+        return self.__repr__()
+    def __repr__(self):
+        return 'TerrastoreBucket(%s,%s)' % (self.server.urls,self.name)
+    
+
+        

File test/pyterrastore/__init__.py

+#############################################################################
+#    Copyright 2010 Dhananjay Nene 
+#    
+#    Licensed under the Apache License, Version 2.0 (the "License"); 
+#    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.
+############################################################################# 

File test/pyterrastore/tests.py

+#############################################################################
+#    Copyright 2010 Dhananjay Nene 
+#    
+#    Licensed under the Apache License, Version 2.0 (the "License"); 
+#    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 pyterrastore.terrastore import TerrastoreServer, TerrastoreBucket
+from unittest import TestCase
+import json
+
+class TestPyTerrastore(TestCase):
+    def setUp(self):
+        tsurl = 'localhost:9511'
+        self.tss = TerrastoreServer(tsurl)
+#        self.sample_json = '{\n}'
+        self.sample_json = '''
+                {"menu": {
+                  "id": "file",
+                  "value": "File",
+                  "popup": {
+                    "menuitem": [
+                      {"value": "New", "onclick": "CreateNewDoc()"},
+                      {"value": "Open", "onclick": "OpenDoc()"},
+                      {"value": "Close", "onclick": "CloseDoc()"}
+                    ]
+                  }
+                }}'''
+        self.cleansed_json = json.dumps(json.loads(self.sample_json))
+        self.sample_doc = json.loads(self.sample_json)
+        
+        self.second_doc = {'hello':'world','greetings':'martians'}
+        self.second_json = json.dumps(self.second_doc)
+        self.second_json_ascii = json.dumps(self.second_doc,encoding="latin-1", ensure_ascii = True)
+        self.cleansed_second_doc = json.loads(json.dumps(self.second_doc,encoding="latin-1",ensure_ascii=True),encoding="latin-1")
+        
+
+        assert self.tss.urls == (tsurl,)
+        for bucket in self.tss.get_buckets() :
+            self.tss.remove_bucket(bucket.name)
+    
+    def testEmpty(self):
+        buckets = self.tss.get_buckets()
+        self.assertEquals(len(buckets), 0,'Bucket size unexpected')
+            
+    def testSimpleBucketOperations(self):
+        # Create bucket
+        bucket_name = 'bucket'
+        bucket_new = self.tss.create_bucket(bucket_name)
+        self.assertEquals(type(bucket_new), TerrastoreBucket)
+        self.assertEquals(bucket_new.name,bucket_name)
+        
+        #fetch bucket
+        buckets = self.tss.get_buckets()
+        self.assertEquals(len(buckets),1)
+        bucket = buckets[0]
+        self.assertEquals(bucket.name,bucket_name)
+        
+        # fetch bucket contents
+        self.assertEquals(bucket.get_contents(),'{}')
+        
+        # Add document to bucket
+        document_key_1 = 'doc1'
+        bucket.put(document_key_1,self.sample_json)
+        
+        # fetch all docs
+        self.assertEquals(bucket.get_contents(),'{"%s":%s}' % (document_key_1,self.sample_json))
+        
+        # check document fetch
+        self.assertEquals(bucket.get_documents().get(document_key_1),self.sample_doc)
+        
+        document_key_2 = 'doc2'
+        bucket.put(document_key_2,self.second_json)
+        retrieved_second_doc = bucket.get_document(document_key_2)
+        self.assertEquals(self.second_doc, retrieved_second_doc)
+        
+        # fetch document string
+        self.assertEquals(bucket.get(document_key_1),self.sample_json)
+        
+        # refetch the same document as an object
+        self.assertEquals( bucket.get_document(document_key_1),self.sample_doc)
+        
+        # remove the document
+        bucket.remove(document_key_1)
+        bucket.remove(document_key_2)
+        self.assertEquals(bucket.get_contents(),'{}')

File versions.txt

+16 Mar 2010 : 0.1.0 - Initial version with a minimal functionality