diff --git a/cloudant/account.py b/cloudant/account.py index 5ad212e..18f5e93 100644 --- a/cloudant/account.py +++ b/cloudant/account.py @@ -6,12 +6,21 @@ class Account(Resource): """ - A account to a Cloudant or CouchDB account. + An account to a Cloudant or CouchDB account. account = cloudant.Account() - account.login(USERNAME, PASSWORD).result() - print account.get().result().json() - # {"couchdb": "Welcome", ...} + response = account.login(USERNAME, PASSWORD) + print response.json() + # { "ok": True, ... } + + Like all Cloudant-Python objects, pass `async=True` + to make asynchronous requests, like this: + + account = cloudant.Account(async=True) + future = account.login(USERNAME, PASSWORD) + response = future.result() + print response.json() + # { "ok": True, ... } """ def __init__(self, uri="http://localhost:5984", **kwargs): @@ -34,7 +43,11 @@ def __delitem__(self, name): Blocks until the response returns, and raises an error if the deletion failed. """ - return self.database(name, **self.opts).delete().result().raise_for_status() + response = self.database(name, **self.opts).delete() + # block until result if the object is using async + if hasattr(response, 'result'): + response = response.result() + response.raise_for_status() def all_dbs(self, **kwargs): """List all databases.""" diff --git a/cloudant/database.py b/cloudant/database.py index 2398c7a..e599631 100644 --- a/cloudant/database.py +++ b/cloudant/database.py @@ -35,7 +35,11 @@ def __getitem__(self, name): def __setitem__(self, name, doc): """Creates `doc` with an ID of `name`.""" - self.put(name, params=doc).result() + response = self.put(name, params=doc) + # block until result if the object is using async + if hasattr(response, 'result'): + response = response.result() + response.raise_for_status() def all_docs(self, **kwargs): """ diff --git a/cloudant/document.py b/cloudant/document.py index 309f45b..aabc9bc 100644 --- a/cloudant/document.py +++ b/cloudant/document.py @@ -23,7 +23,11 @@ def merge(self, change, **kwargs): Merge `change` into the document, and then `PUT` the updated document back to the server. """ - doc = self.get().result().json() + response = self.get() + # block until result if the object is using async + if hasattr(response, 'result'): + response = response.result() + doc = response.json() doc.update(change) return self.put(params=doc, **kwargs) diff --git a/cloudant/index.py b/cloudant/index.py index 152b93d..8c89186 100644 --- a/cloudant/index.py +++ b/cloudant/index.py @@ -21,7 +21,10 @@ class Index(Resource): """ def __iter__(self): - response = self.get(stream=True).result() + response = self.get(stream=True) + # block until result if the object is using async + if hasattr(response, 'result'): + response = response.result() for line in response.iter_lines(): if line: if line[-1] == ',': diff --git a/cloudant/resource.py b/cloudant/resource.py index f9e260b..7bb938e 100644 --- a/cloudant/resource.py +++ b/cloudant/resource.py @@ -1,4 +1,5 @@ from requests_futures.sessions import FuturesSession +import requests import urlparse import json import copy @@ -20,11 +21,14 @@ def __init__(self, uri, **kwargs): self.uri = uri self.uri_parts = urlparse.urlparse(self.uri) - if 'session' in kwargs.keys(): + if kwargs.get('session'): self._session = kwargs['session'] del kwargs['session'] - else: + elif kwargs.get('async'): self._session = FuturesSession() + del kwargs['async'] + else: + self._session = requests.Session() self._set_options(**kwargs) diff --git a/docs/site/index.html b/docs/site/index.html index baa7848..468731b 100644 --- a/docs/site/index.html +++ b/docs/site/index.html @@ -42,7 +42,7 @@
pip install cloudant
Cloudant-Python is an asynchronous wrapper around Python Requests for interacting with CouchDB or Cloudant instances. Check it out:
+Cloudant-Python is a wrapper around Python Requests for interacting with CouchDB or Cloudant instances. Check it out:
import cloudant
# connect to your account
@@ -52,19 +52,34 @@ Usage
# login, so we can make changes
login = account.login(USERNAME, PASSWORD)
-assert login.result().status_code == 200
+assert login.status_code == 200
# create a database object
db = account.database('test')
# now, create the database on the server
-future = db.put()
-response = future.result()
+response = db.put()
print response.json()
# {'ok': True}
-HTTP requests return Future objects, which will await the return of the HTTP response. Call result()
to get the Response object.
HTTP requests return [Response][response] objects, right from Requests.
+Cloudant-Python can also make asynchronous requests by passing async=True
to an object's constructor, like so:
import cloudant
+
+# connect to your account
+# in this case, https://garbados.cloudant.com
+USERNAME = 'garbados'
+account = cloudant.Account(USERNAME, async=True)
+
+# login, so we can make changes
+future = account.login(USERNAME, PASSWORD)
+# block until we get the response body
+login = future.result()
+assert login.status_code == 200
+
+
+Asynchronous HTTP requests return Future objects, which will await the return of the HTTP response. Call result()
to get the Response object.
See the API reference for all the details you could ever want.
Cloudant-Python is minimal, performant, and effortless. Check it out:
@@ -92,11 +107,11 @@http://localhost:5984/DB/_design/DOC/_view/INDEX
-> Account().database(DB).design(DOC).view(INDEX)
HTTP request methods like get
and post
return Future objects, which represent an eventual response. This allows your code to keep executing while the request is off doing its business in cyberspace. To get the Response object (waiting until it arrives if necessary) use the result
method, like so:
If you instantiate an object with the async=True
option, its HTTP request methods (such as get
and post
) will return Future objects, which represent an eventual response. This allows your code to keep executing while the request is off doing its business in cyberspace. To get the Response object (waiting until it arrives if necessary) use the result
method, like so:
import cloudant
account = cloudant.Account()
@@ -186,9 +201,17 @@ API Reference
Account
A account to a Cloudant or CouchDB account.
account = cloudant.Account()
-account.login(USERNAME, PASSWORD).result()
-print account.get().result().json()
-# {"couchdb": "Welcome", ...}
+response = account.login(USERNAME, PASSWORD)
+print response.json()
+# { "ok": True, ... }
+
+Like all Cloudant-Python objects, pass async=True
+to make asynchronous requests, like this:
+account = cloudant.Account(async=True)
+future = account.login(USERNAME, PASSWORD)
+response = future.result()
+print response.json()
+# { "ok": True, ... }
@@ -861,7 +884,11 @@ merge
def merge(self, change, **kwargs):
- doc = self.get().result().json()
+ response = self.get()
+ # block until result if the object is using async
+ if hasattr(response, 'result'):
+ response = response.result()
+ doc = response.json()
doc.update(change)
return self.put(params=doc, **kwargs)
@@ -1090,7 +1117,11 @@ merge
def merge(self, change, **kwargs):
- doc = self.get().result().json()
+ response = self.get()
+ # block until result if the object is using async
+ if hasattr(response, 'result'):
+ response = response.result()
+ doc = response.json()
doc.update(change)
return self.put(params=doc, **kwargs)
diff --git a/readme.md b/readme.md
index bd8d24c..ee9bbf0 100644
--- a/readme.md
+++ b/readme.md
@@ -6,6 +6,7 @@
[](https://crate.io/packages/cloudant/)
[futures]: http://docs.python.org/dev/library/concurrent.futures.html#future-objects
+[requests]: http://www.python-requests.org/en/latest/
[responses]: http://www.python-requests.org/en/latest/api/#requests.Response
An effortless Cloudant / CouchDB interface for Python.
@@ -16,7 +17,7 @@ An effortless Cloudant / CouchDB interface for Python.
## Usage
-Cloudant-Python is an asynchronous wrapper around Python [Requests](http://www.python-requests.org/en/latest/) for interacting with CouchDB or Cloudant instances. Check it out:
+Cloudant-Python is a wrapper around Python [Requests][requests] for interacting with CouchDB or Cloudant instances. Check it out:
```python
import cloudant
@@ -28,19 +29,37 @@ account = cloudant.Account(USERNAME)
# login, so we can make changes
login = account.login(USERNAME, PASSWORD)
-assert login.result().status_code == 200
+assert login.status_code == 200
# create a database object
db = account.database('test')
# now, create the database on the server
-future = db.put()
-response = future.result()
+response = db.put()
print response.json()
# {'ok': True}
```
-HTTP requests return [Future][futures] objects, which will await the return of the HTTP response. Call `result()` to get the [Response][responses] object.
+HTTP requests return [Response][responses] objects, right from [Requests][requests].
+
+Cloudant-Python can also make asynchronous requests by passing `async=True` to an object's constructor, like so:
+
+```python
+import cloudant
+
+# connect to your account
+# in this case, https://garbados.cloudant.com
+USERNAME = 'garbados'
+account = cloudant.Account(USERNAME, async=True)
+
+# login, so we can make changes
+future = account.login(USERNAME, PASSWORD)
+# block until we get the response body
+login = future.result()
+assert login.status_code == 200
+```
+
+Asynchronous HTTP requests return [Future][futures] objects, which will await the return of the HTTP response. Call `result()` to get the [Response][responses] object.
See the [API reference](http://cloudant-labs.github.io/cloudant-python/#api) for all the details you could ever want.
@@ -77,11 +96,11 @@ doc = db.document('test_doc')
resp = doc.put({
'_id': 'hello_world',
'herp': 'derp'
- }).result()
+ })
# delete the document
rev = resp.json()['_rev']
-doc.delete(rev).result()
+doc.delete(rev).raise_for_status()
# but this also creates a document
db['hello_world'] = {'herp': 'derp'}
@@ -120,12 +139,12 @@ If CouchDB has a special endpoint for something, it's in Cloudant-Python as a sp
### Asynchronous
-HTTP request methods like `get` and `post` return [Future][futures] objects, which represent an eventual response. This allows your code to keep executing while the request is off doing its business in cyberspace. To get the [Response][responses] object (waiting until it arrives if necessary) use the `result` method, like so:
+If you instantiate an object with the `async=True` option, its HTTP request methods (such as `get` and `post`) will return [Future][futures] objects, which represent an eventual response. This allows your code to keep executing while the request is off doing its business in cyberspace. To get the [Response][responses] object (waiting until it arrives if necessary) use the `result` method, like so:
```python
import cloudant
-account = cloudant.Account()
+account = cloudant.Account(async=True)
db = account['test']
future = db.put()
response = future.result()
diff --git a/test/__init__.py b/test/__init__.py
index 0bd64cd..1795753 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -9,7 +9,7 @@ class ResourceTest(unittest.TestCase):
def setUp(self):
self.uri = 'http://localhost:5984'
- names = cloudant.Account(self.uri).uuids(4).result().json()['uuids']
+ names = cloudant.Account(self.uri).uuids(4).json()['uuids']
# database names must start with a letter
names = map(lambda name: 'a' + name, names)
self.db_name = names[0]
@@ -26,6 +26,50 @@ def setUp(self):
'name': 'Larry, the Incorrigible Miscreant'
}
+class AsyncTest(ResourceTest):
+
+ def setUp(self):
+ super(AsyncTest, self).setUp()
+ self.account = cloudant.Account(self.uri, async=True)
+ self.database = self.account.database(self.db_name)
+ self.document = self.database.document(self.doc_name)
+
+ def testAccount(self):
+ future = self.account.get()
+ response = future.result()
+ response.raise_for_status()
+
+ def testDatabase(self):
+ future = self.database.put()
+ response = future.result()
+ response.raise_for_status()
+ del self.account[self.db_name]
+
+ def testDocument(self):
+ future = self.database.put()
+ response = future.result()
+ response.raise_for_status()
+ self.database[self.doc_name] = self.test_doc
+ future = self.document.merge(self.test_otherdoc)
+ response = future.result()
+ response.raise_for_status()
+ del self.account[self.db_name]
+
+ def testIndex(self):
+ future = self.database.put()
+ response = future.result()
+ response.raise_for_status()
+
+ future = self.database.bulk_docs(self.test_doc, self.test_otherdoc)
+ response = future.result()
+ response.raise_for_status()
+
+ total = []
+ for doc in self.database:
+ total.append(doc)
+ assert len(total) == 2
+
+ del self.account[self.db_name]
class AccountTest(ResourceTest):
@@ -38,39 +82,39 @@ def testCloudant(self):
assert account.uri == "https://garbados.cloudant.com"
def testAllDbs(self):
- assert self.account.all_dbs().result().status_code == 200
+ assert self.account.all_dbs().status_code == 200
def testSession(self):
- assert self.account.session().result().status_code == 200
+ assert self.account.session().status_code == 200
def testActiveTasks(self):
- assert self.account.active_tasks().result().status_code == 200
+ assert self.account.active_tasks().status_code == 200
def testSecurity(self):
username = 'user'
password = 'password'
# try auth login when admin party is on
- assert self.account.login(username, password).result().status_code == 401
+ assert self.account.login(username, password).status_code == 401
# disable admin party
path = '_config/admins/%s' % username
assert self.account.put(path, data="\"%s\"" %
- password).result().status_code == 200
+ password).status_code == 200
# login, logout
- assert self.account.login(username, password).result().status_code == 200
- assert self.account.logout().result().status_code == 200
+ assert self.account.login(username, password).status_code == 200
+ assert self.account.logout().status_code == 200
# re-enable admin party
- assert self.account.login(username, password).result().status_code == 200
- assert self.account.delete(path).result().status_code == 200
+ assert self.account.login(username, password).status_code == 200
+ assert self.account.delete(path).status_code == 200
def testReplicate(self):
self.db = self.account.database(self.db_name)
- assert self.db.put().result().status_code == 201
+ assert self.db.put().status_code == 201
params = dict(create_target=True)
assert self.account.replicate(
- self.db_name, self.otherdb_name, params=params).result().status_code == 200
+ self.db_name, self.otherdb_name, params=params).status_code == 200
- assert self.db.delete().result().status_code == 200
+ assert self.db.delete().status_code == 200
del self.account[self.otherdb_name]
def testCreateDb(self):
@@ -78,7 +122,7 @@ def testCreateDb(self):
self.account[self.db_name]
def testUuids(self):
- assert self.account.uuids().result().status_code == 200
+ assert self.account.uuids().status_code == 200
class DatabaseTest(ResourceTest):
@@ -89,19 +133,19 @@ def setUp(self):
db_name = '/'.join([self.uri, self.db_name])
self.db = cloudant.Database(db_name)
- response = self.db.put().result()
+ response = self.db.put()
response.raise_for_status()
def testGet(self):
- assert self.db.get().result().status_code == 200
+ assert self.db.get().status_code == 200
def testBulk(self):
assert self.db.bulk_docs(
- self.test_doc, self.test_otherdoc).result().status_code == 201
+ self.test_doc, self.test_otherdoc).status_code == 201
def testIter(self):
assert self.db.bulk_docs(
- self.test_doc, self.test_otherdoc).result().status_code == 201
+ self.test_doc, self.test_otherdoc).status_code == 201
for derp in self.db:
pass
@@ -109,27 +153,27 @@ def testAllDocs(self):
self.db.all_docs()
def testChanges(self):
- assert self.db.changes().result().status_code == 200
+ assert self.db.changes().status_code == 200
assert self.db.changes(params={
'feed': 'continuous'
- }).result().status_code == 200
+ }).status_code == 200
def testViewCleanup(self):
- assert self.db.view_cleanup().result().status_code == 202
+ assert self.db.view_cleanup().status_code == 202
def testRevs(self):
# put some docs
assert self.db.bulk_docs(
- self.test_doc, self.test_otherdoc).result().status_code == 201
+ self.test_doc, self.test_otherdoc).status_code == 201
# get their revisions
revs = defaultdict(list)
for doc in self.db:
revs[doc['id']].append(doc['value']['rev'])
- assert self.db.missing_revs(revs).result().status_code == 200
- assert self.db.revs_diff(revs).result().status_code == 200
+ assert self.db.missing_revs(revs).status_code == 200
+ assert self.db.revs_diff(revs).status_code == 200
def tearDown(self):
- assert self.db.delete().result().status_code == 200
+ assert self.db.delete().status_code == 200
class DocumentTest(ResourceTest):
@@ -137,29 +181,29 @@ class DocumentTest(ResourceTest):
def setUp(self):
super(DocumentTest, self).setUp()
self.db = cloudant.Database('/'.join([self.uri, self.db_name]))
- assert self.db.put().result().status_code == 201
+ assert self.db.put().status_code == 201
self.doc = self.db.document(self.doc_name)
def testCrud(self):
- assert self.doc.put(params=self.test_doc).result().status_code == 201
- resp = self.doc.get().result()
+ assert self.doc.put(params=self.test_doc).status_code == 201
+ resp = self.doc.get()
assert resp.status_code == 200
rev = resp.json()['_rev']
- assert self.doc.delete(rev).result().status_code == 200
+ assert self.doc.delete(rev).status_code == 200
def testDict(self):
self.db[self.doc_name] = self.test_doc
self.db[self.doc_name]
def testMerge(self):
- assert self.doc.put(params=self.test_doc).result().status_code == 201
- assert self.doc.merge(self.test_otherdoc).result().status_code == 201
+ assert self.doc.put(params=self.test_doc).status_code == 201
+ assert self.doc.merge(self.test_otherdoc).status_code == 201
def testAttachment(self):
self.doc.attachment('file')
def tearDown(self):
- assert self.db.delete().result().status_code == 200
+ assert self.db.delete().status_code == 200
class DesignTest(ResourceTest):
@@ -167,9 +211,9 @@ class DesignTest(ResourceTest):
def setUp(self):
super(DesignTest, self).setUp()
self.db = cloudant.Database('/'.join([self.uri, self.db_name]))
- assert self.db.put().result().status_code == 201
+ assert self.db.put().status_code == 201
self.doc = self.db.design('ddoc')
- assert self.doc.put(params=self.test_doc).result().status_code == 201
+ assert self.doc.put(params=self.test_doc).status_code == 201
def testView(self):
self.doc.index('_view/derp')
@@ -178,11 +222,11 @@ def testView(self):
def testList(self):
# todo: test on actual list and show functions
- assert self.doc.list('herp', 'derp').result().status_code == 404
- assert self.doc.show('herp', 'derp').result().status_code == 500
+ assert self.doc.list('herp', 'derp').status_code == 404
+ assert self.doc.show('herp', 'derp').status_code == 500
def tearDown(self):
- assert self.db.delete().result().status_code == 200
+ assert self.db.delete().status_code == 200
class AttachmentTest(ResourceTest):
@@ -194,7 +238,7 @@ class IndexTest(ResourceTest):
def setUp(self):
super(IndexTest, self).setUp()
self.db = cloudant.Database('/'.join([self.uri, self.db_name]))
- assert self.db.put().result().status_code == 201
+ assert self.db.put().status_code == 201
self.doc = self.db.document(self.doc_name)
def testPrimaryIndex(self):
@@ -202,12 +246,12 @@ def testPrimaryIndex(self):
Show that views can be used as iterators
"""
for doc in [self.test_doc, self.test_otherdoc]:
- assert self.db.post(params=doc).result().status_code == 201
+ assert self.db.post(params=doc).status_code == 201
for derp in self.db.all_docs():
pass
def tearDown(self):
- assert self.db.delete().result().status_code == 200
+ assert self.db.delete().status_code == 200
class ErrorTest(ResourceTest):
@@ -217,7 +261,7 @@ def setUp(self):
self.db = cloudant.Database('/'.join([self.uri, self.db_name]))
def testMissing(self):
- response = self.db.get().result()
+ response = self.db.get()
assert response.status_code == 404
if __name__ == "__main__":