diff --git a/README.md b/README.md index 74d5d33..489f662 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Implements the CouchDB API for twisted. _Enhancements_ +* No more bound databases: if no database parameter is passed, the database given during client creation is used. * Added CouchDB authentication support (supply username and password args when instantiating) * Re-factored underlying HTTP transport to use Twisted Web Agent (will allow connection pooling in the future). * Re-factored code into formal Python package. API compatible with old package layout. diff --git a/paisley/changes.py b/paisley/changes.py index 36bcad8..9b6c4f7 100644 --- a/paisley/changes.py +++ b/paisley/changes.py @@ -64,9 +64,10 @@ def connectionLost(self, reason): class ChangeNotifier(object): - def __init__(self, db, dbName, since=None): + def __init__(self, db, dbName=None, since=None): self._db = db - self._dbName = dbName + # if dbName is None, we assume that db is already bound + self._dbName = dbName self._caches = [] self._listeners = [] @@ -105,11 +106,7 @@ def setSince(info): d.addCallback(setSince) def requestChanges(): - kwargs['feed'] = 'continuous' - kwargs['since'] = self._since - # FIXME: str should probably be unicode, as dbName can be - url = str(self._db.url_template % - '/%s/_changes?%s' % (self._dbName, urlencode(kwargs))) + url = self._db.changesUrl(self._dbName, feed='continuous', since = self._since) return self._db.client.request('GET', url) d.addCallback(lambda _: requestChanges()) diff --git a/paisley/client.py b/paisley/client.py index 3b42ef9..7301714 100644 --- a/paisley/client.py +++ b/paisley/client.py @@ -131,8 +131,7 @@ def __init__(self, host, port=5984, dbName=None, username=None, password=None, d self.username = username self.password =password self.url_template = "http://%s:%s%%s" % (self.host, self.port) - if dbName is not None: - self.bindToDB(dbName) + self.dbName = dbName if disable_log: # since this is the db layer, and we generate a lot of logs, @@ -155,7 +154,6 @@ def nullfn(self, *a, **k): port, dbName if dbName else '') - def parseResult(self, result): """ Parse JSON result from the DB. @@ -163,26 +161,22 @@ def parseResult(self, result): return json.loads(result) - def bindToDB(self, dbName): - """ - Bind all operations asking for a DB name to the given DB. - """ - for methname in ["createDB", "deleteDB", "infoDB", "listDoc", - "openDoc", "saveDoc", "deleteDoc", "openView", - "tempView"]: - method = getattr(self, methname) - newMethod = partial(method, dbName) - setattr(self, methname, newMethod) - - # Database operations - def createDB(self, dbName): + def changesUrl(self, dbName=None, **kwargs): + if dbName == None : dbName = self.dbName + # FIXME: str should probably be unicode, as dbName can be + url = str(self.url_template % + '/%s/_changes?%s' % (dbName, urlencode(kwargs))) + return url + + def createDB(self, dbName=None): """ Creates a new database on the server. @type dbName: str """ + if dbName == None : dbName = self.dbName # Responses: {u'ok': True}, 409 Conflict, 500 Internal Server Error, # 401 Unauthorized # 400 {"error":"illegal_database_name","reason":"Only lowercase @@ -193,12 +187,13 @@ def createDB(self, dbName): ).addCallback(self.parseResult) - def deleteDB(self, dbName): + def deleteDB(self, dbName=None): """ Deletes the database on the server. @type dbName: str """ + if dbName == None : dbName = self.dbName # Responses: {u'ok': True}, 404 Object Not Found return self.delete("/%s/" % (dbName,) ).addCallback(self.parseResult) @@ -212,10 +207,11 @@ def listDB(self): return self.get("/_all_dbs", descr='listDB').addCallback(self.parseResult) - def infoDB(self, dbName): + def infoDB(self, dbName=None): """ Returns info about the couchDB. """ + if dbName == None : dbName = self.dbName # Responses: {u'update_seq': 0, u'db_name': u'mydb', u'doc_count': 0} # 404 Object Not Found return self.get("/%s/" % (dbName,), descr='infoDB' @@ -224,10 +220,11 @@ def infoDB(self, dbName): # Document operations - def listDoc(self, dbName, reverse=False, startKey=0, count=-1): + def listDoc(self, dbName=None, reverse=False, startKey=0, count=-1): """ List all documents in a given database. """ + if dbName == None : dbName = self.dbName # Responses: {u'rows': [{u'_rev': -1825937535, u'_id': u'mydoc'}], # u'view': u'_all_docs'}, 404 Object Not Found uri = "/%s/_all_docs" % (dbName,) @@ -244,7 +241,7 @@ def listDoc(self, dbName, reverse=False, startKey=0, count=-1): ).addCallback(self.parseResult) - def openDoc(self, dbName, docId, revision=None, full=False, attachment=""): + def openDoc(self, docId, dbName=None, revision=None, full=False, attachment=""): """ Open a document in a given database. @@ -264,6 +261,7 @@ def openDoc(self, dbName, docId, revision=None, full=False, attachment=""): # Responses: {u'_rev': -1825937535, u'_id': u'mydoc', ...} # 404 Object Not Found + if dbName == None : dbName = self.dbName # FIXME: remove these conversions and have our callers do them docId = unicode(docId) assert type(docId) is unicode, \ @@ -303,7 +301,7 @@ def addAttachments(self, document, attachments): document["_attachments"][name] = {"type": "base64", "data": data} - def saveDoc(self, dbName, body, docId=None): + def saveDoc(self, body, dbName=None, docId=None): """ Save/create a document to/in a given database. @@ -320,6 +318,7 @@ def saveDoc(self, dbName, body, docId=None): # 404 Object not found (if database does not exist) # 409 Conflict, 500 Internal Server Error + if dbName == None : dbName = self.dbName if docId: # FIXME: remove these conversions and have our callers do them docId = unicode(docId) @@ -335,7 +334,7 @@ def saveDoc(self, dbName, body, docId=None): return d.addCallback(self.parseResult) - def deleteDoc(self, dbName, docId, revision): + def deleteDoc(self, docId, revision, dbName=None): """ Delete a document on given database. @@ -352,6 +351,7 @@ def deleteDoc(self, dbName, docId, revision): # Responses: {u'_rev': 1469561101, u'ok': True} # 500 Internal Server Error + if dbName == None : dbName = self.dbName docId = unicode(docId) assert type(docId) is unicode, \ 'docId is %r instead of unicode' % (type(docId), ) @@ -370,12 +370,13 @@ def deleteDoc(self, dbName, docId, revision): # View operations - def openView(self, dbName, docId, viewId, **kwargs): + def openView(self, docId, viewId, dbName=None, **kwargs): """ Open a view of a document in a given database. """ # Responses: # 500 Internal Server Error (illegal database name) + if dbName == None : dbName = self.dbName def buildUri(dbName=dbName, docId=docId, viewId=viewId, kwargs=kwargs): return "/%s/_design/%s/_view/%s?%s" % ( dbName, quote(docId), viewId, urlencode(kwargs)) @@ -415,10 +416,11 @@ def addViews(self, document, views): document["views"][name] = data - def tempView(self, dbName, view): + def tempView(self, view, dbName=None): """ Make a temporary view on the server. """ + if dbName == None : dbName = self.dbName d = self.post("/%s/_temp_view" % (dbName,), view, descr='tempView') return d.addCallback(self.parseResult) diff --git a/paisley/test/test_client.py b/paisley/test/test_client.py index 8ceac84..ab50b0b 100644 --- a/paisley/test/test_client.py +++ b/paisley/test/test_client.py @@ -232,7 +232,7 @@ def test_openDoc(self): """ Test openDoc. """ - d = self.client.openDoc("mydb", "mydoc") + d = self.client.openDoc("mydoc", "mydb") self.assertEquals(self.client.uri, "/mydb/mydoc") self.assertEquals(self.client.kwargs["method"], "GET") return self._checkParseDeferred(d) @@ -242,7 +242,7 @@ def test_openDocAtRevision(self): """ Test openDoc with a specific revision. """ - d = self.client.openDoc("mydb", "mydoc", revision="ABC") + d = self.client.openDoc("mydoc", "mydb", revision="ABC") self.assertEquals(self.client.uri, "/mydb/mydoc?rev=ABC") self.assertEquals(self.client.kwargs["method"], "GET") return self._checkParseDeferred(d) @@ -252,7 +252,7 @@ def test_openDocWithRevisionHistory(self): """ Test openDoc with revision history. """ - d = self.client.openDoc("mydb", "mydoc", full=True) + d = self.client.openDoc("mydoc", "mydb", full=True) self.assertEquals(self.client.uri, "/mydb/mydoc?full=true") self.assertEquals(self.client.kwargs["method"], "GET") return self._checkParseDeferred(d) @@ -262,7 +262,7 @@ def test_openDocAttachment(self): """ Test openDoc for an attachment. """ - d = self.client.openDoc("mydb", "mydoc", attachment="bar") + d = self.client.openDoc("mydoc", "mydb", attachment="bar") self.assertEquals(self.client.uri, "/mydb/mydoc/bar") self.assertEquals(self.client.kwargs["method"], "GET") # Data is transfered without parsing @@ -274,7 +274,7 @@ def test_saveDocWithDocId(self): """ Test saveDoc, giving an explicit document ID. """ - d = self.client.saveDoc("mydb", "mybody", "mydoc") + d = self.client.saveDoc("mybody", "mydb", "mydoc") self.assertEquals(self.client.uri, "/mydb/mydoc") self.assertEquals(self.client.kwargs["method"], "PUT") return self._checkParseDeferred(d) @@ -284,7 +284,7 @@ def test_saveDocWithoutDocId(self): """ Test saveDoc without a document ID. """ - d = self.client.saveDoc("mydb", "mybody") + d = self.client.saveDoc("mybody", "mydb") self.assertEquals(self.client.uri, "/mydb/") self.assertEquals(self.client.kwargs["method"], "POST") return self._checkParseDeferred(d) @@ -294,7 +294,7 @@ def test_saveStructuredDoc(self): """ saveDoc should automatically serialize a structured document. """ - d = self.client.saveDoc("mydb", {"value": "mybody", "_id": "foo"}, "mydoc") + d = self.client.saveDoc({"value": "mybody", "_id": "foo"}, "mydb", "mydoc") self.assertEquals(self.client.uri, "/mydb/mydoc") self.assertEquals(self.client.kwargs["method"], "PUT") return self._checkParseDeferred(d) @@ -304,7 +304,7 @@ def test_deleteDoc(self): """ Test deleteDoc. """ - d = self.client.deleteDoc("mydb", "mydoc", "1234567890") + d = self.client.deleteDoc("mydoc", "1234567890", "mydb") self.assertEquals(self.client.uri, "/mydb/mydoc?rev=1234567890") self.assertEquals(self.client.kwargs["method"], "DELETE") return self._checkParseDeferred(d) @@ -325,7 +325,7 @@ def test_openView(self): """ Test openView. """ - d = self.client.openView("mydb", "viewdoc", "myview") + d = self.client.openView("viewdoc", "myview", "mydb") self.assertEquals(self.client.uri, "/mydb/_design/viewdoc/_view/myview?") self.assertEquals(self.client.kwargs["method"], "GET") return self._checkParseDeferred(d) @@ -335,9 +335,9 @@ def test_openViewWithQuery(self): """ Test openView with query arguments. """ - d = self.client.openView("mydb", - "viewdoc", + d = self.client.openView("viewdoc", "myview", + "mydb", startkey="foo", count=10) self.assertEquals(self.client.kwargs["method"], "GET") @@ -356,9 +356,9 @@ def test_openViewWithKeysQuery(self): """ Test openView handles couchdb's strange requirements for keys arguments """ - d = self.client.openView("mydb2", - "viewdoc2", + d = self.client.openView("viewdoc2", "myview2", + "mydb2", keys=[1,3,4, "hello, world", {1: 5}], count=5) self.assertEquals(self.client.kwargs["method"], "POST") @@ -373,7 +373,7 @@ def test_tempView(self): """ Test tempView. """ - d = self.client.tempView("mydb", "js code") + d = self.client.tempView("js code", "mydb") self.assertEquals(self.client.uri, "/mydb/_temp_view") self.assertEquals(self.client.kwargs["postdata"], "js code") self.assertEquals(self.client.kwargs["method"], "POST") @@ -389,18 +389,8 @@ def test_addViews(self): self.assertEquals(doc["views"], {"view1": "js code 1", "view2": "js code 2"}) - def test_bindToDB(self): - """ - Test bindToDB, calling a bind method afterwards. - """ - self.client.bindToDB("mydb") - d = self.client.listDoc() - self.assertEquals(self.client.uri, "/mydb/_all_docs") - self.assertEquals(self.client.kwargs["method"], "GET") - return self._checkParseDeferred(d) - def test_escapeId(self): - d = self.client.openDoc("mydb", "my doc with spaces") + d = self.client.openDoc("my doc with spaces", "mydb") self.assertEquals(self.client.uri, "/mydb/my%20doc%20with%20spaces") self.assertEquals(self.client.kwargs["method"], "GET") return self._checkParseDeferred(d) @@ -471,7 +461,7 @@ def listCb(result): self.failUnless('test' in result) self.failUnless('_users' in result) d.addCallback(listCb) - d.addCallback(lambda _: self.db.saveDoc('test', {'number': 1}, '1')) + d.addCallback(lambda _: self.db.saveDoc({'number': 1}, 'test', '1')) def saveDoc(result): self.assertEquals(result[u'ok'], True) self.assertEquals(result[u'id'], u'1') @@ -480,7 +470,7 @@ def saveDoc(result): d.addCallback(saveDoc) doc = {} self.db.addViews(doc, {'test': {'map': 'function (doc) { emit(doc.number, doc) }'}}) - d.addCallback(lambda _: self.db.saveDoc('test', doc, '_design/test')) + d.addCallback(lambda _: self.db.saveDoc(doc, 'test', '_design/test')) def addViewCb(result): self.assertEquals(result[u'ok'], True) d.addCallback(addViewCb) @@ -548,11 +538,12 @@ def testUnicodeContents(self): d = defer.Deferred() - d.addCallback(lambda _: self.db.saveDoc('test', { + d.addCallback(lambda _: self.db.saveDoc({ 'name': name, name: 'name', - })) - d.addCallback(lambda r: self.db.openDoc('test', r['id'])) + }, 'test' + )) + d.addCallback(lambda r: self.db.openDoc(r['id'], 'test')) def check(r): self.assertEquals(r['name'], name) self.assertEquals(r[name], u'name') @@ -567,13 +558,13 @@ def testUnicodeId(self): d = defer.Deferred() - d.addCallback(lambda _: self.db.saveDoc('test', { + d.addCallback(lambda _: self.db.saveDoc({ 'name': 'name', - }, docId=docId)) + }, 'test', docId=docId)) def saveDocCb(r): self.assertEquals(r['id'], docId) - return self.db.openDoc('test', r['id']) + return self.db.openDoc(r['id'], 'test') d.addCallback(saveDocCb) def check(r): @@ -584,7 +575,7 @@ def check(r): self.assertEquals(type(r[u'_rev']), unicode) # open again, with revision - return self.db.openDoc('test', r['_id'], revision=r['_rev']) + return self.db.openDoc(r['_id'], 'test', revision=r['_rev']) d.addCallback(check) def checkRevisioned(r): @@ -597,7 +588,7 @@ def checkRevisioned(r): d.addCallback(checkRevisioned) d.addCallback(lambda r: self.db.deleteDoc( - 'test', r[u'_id'], r[u'_rev'])) + r[u'_id'], r[u'_rev'], 'test')) d.callback(None) return d