Revision: 195
Author: cmlenz
Date: Tue Sep 8 14:17:41 2009
Log: Experimental httplib branch: remove httplib2 requirement from
setup.py, fix for connection retry on broken pipe error, disable full
commit in tests, etc.
http://code.google.com/p/couchdb-python/source/detail?r=195
Modified:
/branches/experimental/httplib/couchdb/client.py
/branches/experimental/httplib/couchdb/http.py
/branches/experimental/httplib/couchdb/tests/client.py
/branches/experimental/httplib/couchdb/tests/couch_tests.py
/branches/experimental/httplib/couchdb/tests/schema.py
/branches/experimental/httplib/setup.py
=======================================
--- /branches/experimental/httplib/couchdb/client.py Wed Aug 12 15:12:40
2009
+++ /branches/experimental/httplib/couchdb/client.py Tue Sep 8 14:17:41
2009
@@ -68,7 +68,8 @@
>>> del server['python-tests']
"""
- def __init__(self, url=DEFAULT_BASE_URL, cache=None, timeout=None):
+ def __init__(self, url=DEFAULT_BASE_URL, cache=None, timeout=None,
+ full_commit=True):
"""Initialize the server object.
:param uri: the URI of the server (for example
@@ -84,6 +85,8 @@
self.resource = http.Resource(url, session)
else:
self.resource = url # treat as a Resource object
+ if not full_commit:
+ self.resource.headers['X-Couch-Full-Commit'] = 'false'
def __contains__(self, name):
"""Return whether the server contains a database with the specified
@@ -339,6 +342,14 @@
_, _, data =
self.resource.post(body=data)
return data['id']
+ def commit(self):
+ """If the server is configured to delay commits, or previous
requests
+ used the special ``X-Couch-Full-Commit: false`` header to disable
+ immediate commits, this method can be used to ensure that any
+ non-committed changes are committed to physical storage.
+ """
+
self.resource.post('_ensure_full_commit')
+
def compact(self):
"""Compact the database.
=======================================
--- /branches/experimental/httplib/couchdb/http.py Fri Aug 21 01:25:55 2009
+++ /branches/experimental/httplib/couchdb/http.py Tue Sep 8 14:17:41 2009
@@ -164,6 +164,7 @@
conn.sock.sendall(('%x\r\n' % len(chunk)) +
chunk + '\r\n')
conn.sock.sendall('0\r\n\r\n')
+ return conn.getresponse()
except socket.error, e:
ecode = e.args[0]
if retries > 0 and ecode == 54: # reset by peer
@@ -175,8 +176,7 @@
else:
raise
- _try_request()
- resp = conn.getresponse()
+ resp = _try_request()
status = resp.status
# Handle authentication challenge
@@ -185,15 +185,17 @@
auth_header = resp.getheader('www-authenticate', '')
if auth_header:
if self._authenticate(auth_header, headers, credentials):
- _try_request()
- resp = conn.getresponse()
+ resp = _try_request()
status = resp.status
# Handle conditional response
if status == 304 and method in ('GET', 'HEAD'):
resp.read()
self._return_connection(url, conn)
- return cached_resp
+ status, msg, data, decoded = cached_resp
+ if data is not None and not decoded:
+ data = StringIO(data)
+ return status, msg, data
elif cached_resp:
del self.cache[url]
@@ -213,6 +215,8 @@
num_redirects=num_redirects + 1)
data = None
+ decoded = False
+ streamed = False
# Read the full response for empty responses so that the
connection is
# in good state for the next request
@@ -224,17 +228,20 @@
# Automatically decode JSON response bodies
elif resp.getheader('content-type') == 'application/json':
data = json.decode(resp.read())
+ decoded = True
self._return_connection(url, conn)
# Buffer small non-JSON response bodies
elif int(resp.getheader('content-length', sys.maxint)) <
CHUNK_SIZE:
data = resp.read()
+ self._return_connection(url, conn)
# For large or chunked response bodies, do not buffer the full
body,
# and instead return a minimal file-like object
else:
data = ResponseBody(resp,
lambda: self._return_connection(url, conn))
+ streamed = True
# Handle errors
if status >= 400:
@@ -257,13 +264,16 @@
raise ServerError((status, error))
# Store cachable responses
- if method in ('GET', 'HEAD') and 'etag' in resp.msg and \
- not isinstance(data, ResponseBody):
- self.cache[url] = (status, resp.msg, data)
+ if not streamed and method in ('GET', 'HEAD') and 'etag' in
resp.msg:
+ self.cache[url] = (status, resp.msg, data, decoded)
+
+ if not decoded and not streamed and data is not None:
+ data = StringIO(data)
return status, resp.msg, data
def _authenticate(self, info, headers, credentials):
+ # Naive Basic authentication support
match = re.match(r'''(\w*)\s+realm=['"]([^'"]+)['"]''', info)
if match:
scheme, realm = match.groups()
@@ -290,7 +300,6 @@
conn = cls(host)
finally:
self.lock.release()
-
return conn
def _return_connection(self, url, conn):
@@ -304,12 +313,18 @@
class Resource(object):
- def __init__(self, url, session):
+ def __init__(self, url, session, headers=None):
self.url, self.credentials = extract_credentials(url)
+ if session is None:
+ session = Session()
self.session = session
+ self.headers = headers or {}
def __call__(self, *path):
- return type(self)(urljoin(self.url, *path), self.session)
+ obj = type(self)(urljoin(self.url, *path), self.session)
+ obj.credentials = self.credentials
+ obj.headers = self.headers.copy()
+ return obj
def delete(self, path=None, headers=None, **params):
return self._request('DELETE', path, headers=headers, **params)
@@ -328,8 +343,10 @@
return self._request('PUT', path, body=body, headers=headers,
**params)
def _request(self, method, path=None, body=None, headers=None,
**params):
+ all_headers = self.headers.copy()
+ all_headers.update(headers or {})
return self.session.request(method, urljoin(self.url, path,
**params),
- body=body, headers=headers,
+ body=body, headers=all_headers,
credentials=self.credentials)
=======================================
--- /branches/experimental/httplib/couchdb/tests/client.py Fri Jul 3
09:55:25 2009
+++ /branches/experimental/httplib/couchdb/tests/client.py Tue Sep 8
14:17:41 2009
@@ -8,8 +8,8 @@
import doctest
import os
+from StringIO import StringIO
import unittest
-import StringIO
from couchdb import client, http
@@ -18,7 +18,7 @@
def setUp(self):
uri = os.environ.get('COUCHDB_URI', client.DEFAULT_BASE_URL)
- self.server = client.Server(uri)
+ self.server = client.Server(uri, full_commit=False)
def tearDown(self):
if 'python-tests' in self.server:
@@ -52,7 +52,7 @@
def setUp(self):
uri = os.environ.get('COUCHDB_URI', client.DEFAULT_BASE_URL)
- self.server = client.Server(uri)
+ self.server = client.Server(uri, full_commit=False)
if 'python-tests' in self.server:
del self.server['python-tests']
self.db = self.server.create('python-tests')
@@ -61,127 +61,127 @@
if 'python-tests' in self.server:
del self.server['python-tests']
-# def test_create_large_doc(self):
-# self.db['foo'] = {'data': '0123456789' * 110 * 1024} # 10 MB
-# self.assertEqual('foo', self.db['foo']['_id'])
-#
-# def test_doc_id_quoting(self):
-# self.db['foo/bar'] = {'foo': 'bar'}
-# self.assertEqual('bar', self.db['foo/bar']['foo'])
-# del self.db['foo/bar']
-# self.assertEqual(None, self.db.get('foo/bar'))
-#
-# def test_unicode(self):
-# self.db[u'føø'] = {u'bår':
u'Iñtërnâtiônàlizætiøn', 'baz': 'ASCII'}
-# self.assertEqual(u'Iñtërnâtiônàlizætiøn', self.db[u'føø'][u'bår'])
-# self.assertEqual(u'ASCII', self.db[u'føø'][u'baz'])
-#
-# def test_disallow_nan(self):
-# try:
-# self.db['foo'] = {u'number': float('nan')}
-# self.fail('Expected ValueError')
-# except ValueError, e:
-# pass
-#
-# def test_doc_revs(self):
-# doc = {'bar': 42}
-# self.db['foo'] = doc
-# old_rev = doc['_rev']
-# doc['bar'] = 43
-# self.db['foo'] = doc
-# new_rev = doc['_rev']
-#
-# new_doc = self.db.get('foo')
-# self.assertEqual(new_rev, new_doc['_rev'])
-# new_doc = self.db.get('foo', rev=new_rev)
-# self.assertEqual(new_rev, new_doc['_rev'])
-# old_doc = self.db.get('foo', rev=old_rev)
-# self.assertEqual(old_rev, old_doc['_rev'])
-#
-# self.assertTrue(self.db.compact())
-# while
self.db.info()['compact_running']:
-# pass
-# self.assertRaises(http.ServerError, self.db.get, 'foo',
rev=old_rev)
-#
-# def test_attachment_crud(self):
-# doc = {'bar': 42}
-# self.db['foo'] = doc
-# old_rev = doc['_rev']
-#
-# self.db.put_attachment(doc, 'Foo bar', 'foo.txt', 'text/plain')
-# self.assertNotEquals(old_rev, doc['_rev'])
-#
-# doc = self.db['foo']
-# attachment = doc['_attachments']['foo.txt']
-# self.assertEqual(len('Foo bar'), attachment['length'])
-# self.assertEqual('text/plain', attachment['content_type'])
-#
-# self.assertEqual('Foo bar',
-# self.db.get_attachment(doc, 'foo.txt').read())
-# self.assertEqual('Foo bar',
-# self.db.get_attachment('foo', 'foo.txt').read())
-#
-# old_rev = doc['_rev']
-# self.db.delete_attachment(doc, 'foo.txt')
-# self.assertNotEquals(old_rev, doc['_rev'])
-# self.assertEqual(None, self.db['foo'].get('_attachments'))
-#
-# def test_attachment_crud_with_files(self):
-# doc = {'bar': 42}
-# self.db['foo'] = doc
-# old_rev = doc['_rev']
-# f = StringIO.StringIO('Foo bar baz')
-#
-# self.db.put_attachment(doc, f, 'foo.txt')
-# self.assertNotEquals(old_rev, doc['_rev'])
-#
-# doc = self.db['foo']
-# attachment = doc['_attachments']['foo.txt']
-# self.assertEqual(len('Foo bar baz'), attachment['length'])
-# self.assertEqual('text/plain', attachment['content_type'])
-#
-# self.assertEqual('Foo bar baz',
-# self.db.get_attachment(doc, 'foo.txt').read())
-# self.assertEqual('Foo bar baz',
-# self.db.get_attachment('foo', 'foo.txt').read())
-#
-# old_rev = doc['_rev']
-# self.db.delete_attachment(doc, 'foo.txt')
-# self.assertNotEquals(old_rev, doc['_rev'])
-# self.assertEqual(None, self.db['foo'].get('_attachments'))
-#
-# def test_empty_attachment(self):
-# doc = {}
-# self.db['foo'] = doc
-# old_rev = doc['_rev']
-#
-# self.db.put_attachment(doc, '', 'empty.txt')
-# self.assertNotEquals(old_rev, doc['_rev'])
-#
-# doc = self.db['foo']
-# attachment = doc['_attachments']['empty.txt']
-# self.assertEqual(0, attachment['length'])
-#
-# def test_include_docs(self):
-# doc = {'foo': 42, 'bar': 40}
-# self.db['foo'] = doc
-#
-# rows = list(self.db.query(
-# 'function(doc) { emit(doc._id, null); }',
-# include_docs=True
-# ))
-# self.assertEqual(1, len(rows))
-# self.assertEqual(doc, rows[0].doc)
-#
-# def test_query_multi_get(self):
-# for i in range(1, 6):
-# self.db.create({'i': i})
-# res = list(self.db.query('function(doc) { emit(doc.i, null); }',
-# keys=range(1, 6, 2)))
-# self.assertEqual(3, len(res))
-# for idx, i in enumerate(range(1, 6, 2)):
-# self.assertEqual(i, res[idx].key)
-#
+ def test_create_large_doc(self):
+ self.db['foo'] = {'data': '0123456789' * 110 * 1024} # 10 MB
+ self.assertEqual('foo', self.db['foo']['_id'])
+
+ def test_doc_id_quoting(self):
+ self.db['foo/bar'] = {'foo': 'bar'}
+ self.assertEqual('bar', self.db['foo/bar']['foo'])
+ del self.db['foo/bar']
+ self.assertEqual(None, self.db.get('foo/bar'))
+
+ def test_unicode(self):
+ self.db[u'føø'] = {u'bår': u'Iñtërnâtiônàlizætiøn', 'baz': 'ASCII'}
+ self.assertEqual(u'Iñtërnâtiônàlizætiøn', self.db[u'føø'][u'bår'])
+ self.assertEqual(u'ASCII', self.db[u'føø'][u'baz'])
+
+ def test_disallow_nan(self):
+ try:
+ self.db['foo'] = {u'number': float('nan')}
+ self.fail('Expected ValueError')
+ except ValueError, e:
+ pass
+
+ def test_doc_revs(self):
+ doc = {'bar': 42}
+ self.db['foo'] = doc
+ old_rev = doc['_rev']
+ doc['bar'] = 43
+ self.db['foo'] = doc
+ new_rev = doc['_rev']
+
+ new_doc = self.db.get('foo')
+ self.assertEqual(new_rev, new_doc['_rev'])
+ new_doc = self.db.get('foo', rev=new_rev)
+ self.assertEqual(new_rev, new_doc['_rev'])
+ old_doc = self.db.get('foo', rev=old_rev)
+ self.assertEqual(old_rev, old_doc['_rev'])
+
+ self.assertTrue(self.db.compact())
+ while
self.db.info()['compact_running']:
+ pass
+ self.assertRaises(http.ServerError, self.db.get, 'foo',
rev=old_rev)
+
+ def test_attachment_crud(self):
+ doc = {'bar': 42}
+ self.db['foo'] = doc
+ old_rev = doc['_rev']
+
+ self.db.put_attachment(doc, 'Foo bar', 'foo.txt', 'text/plain')
+ self.assertNotEquals(old_rev, doc['_rev'])
+
+ doc = self.db['foo']
+ attachment = doc['_attachments']['foo.txt']
+ self.assertEqual(len('Foo bar'), attachment['length'])
+ self.assertEqual('text/plain', attachment['content_type'])
+
+ self.assertEqual('Foo bar',
+ self.db.get_attachment(doc, 'foo.txt').read())
+ self.assertEqual('Foo bar',
+ self.db.get_attachment('foo', 'foo.txt').read())
+
+ old_rev = doc['_rev']
+ self.db.delete_attachment(doc, 'foo.txt')
+ self.assertNotEquals(old_rev, doc['_rev'])
+ self.assertEqual(None, self.db['foo'].get('_attachments'))
+
+ def test_attachment_crud_with_files(self):
+ doc = {'bar': 42}
+ self.db['foo'] = doc
+ old_rev = doc['_rev']
+ fileobj = StringIO('Foo bar baz')
+
+ self.db.put_attachment(doc, fileobj, 'foo.txt')
+ self.assertNotEquals(old_rev, doc['_rev'])
+
+ doc = self.db['foo']
+ attachment = doc['_attachments']['foo.txt']
+ self.assertEqual(len('Foo bar baz'), attachment['length'])
+ self.assertEqual('text/plain', attachment['content_type'])
+
+ self.assertEqual('Foo bar baz',
+ self.db.get_attachment(doc, 'foo.txt').read())
+ self.assertEqual('Foo bar baz',
+ self.db.get_attachment('foo', 'foo.txt').read())
+
+ old_rev = doc['_rev']
+ self.db.delete_attachment(doc, 'foo.txt')
+ self.assertNotEquals(old_rev, doc['_rev'])
+ self.assertEqual(None, self.db['foo'].get('_attachments'))
+
+ def test_empty_attachment(self):
+ doc = {}
+ self.db['foo'] = doc
+ old_rev = doc['_rev']
+
+ self.db.put_attachment(doc, '', 'empty.txt')
+ self.assertNotEquals(old_rev, doc['_rev'])
+
+ doc = self.db['foo']
+ attachment = doc['_attachments']['empty.txt']
+ self.assertEqual(0, attachment['length'])
+
+ def test_include_docs(self):
+ doc = {'foo': 42, 'bar': 40}
+ self.db['foo'] = doc
+
+ rows = list(self.db.query(
+ 'function(doc) { emit(doc._id, null); }',
+ include_docs=True
+ ))
+ self.assertEqual(1, len(rows))
+ self.assertEqual(doc, rows[0].doc)
+
+ def test_query_multi_get(self):
+ for i in range(1, 6):
+ self.db.create({'i': i})
+ res = list(self.db.query('function(doc) { emit(doc.i, null); }',
+ keys=range(1, 6, 2)))
+ self.assertEqual(3, len(res))
+ for idx, i in enumerate(range(1, 6, 2)):
+ self.assertEqual(i, res[idx].key)
+
def test_view_multi_get(self):
for i in range(1, 6):
self.db.create({'i': i})
=======================================
--- /branches/experimental/httplib/couchdb/tests/couch_tests.py Fri Jul 3
09:55:25 2009
+++ /branches/experimental/httplib/couchdb/tests/couch_tests.py Tue Sep 8
14:17:41 2009
@@ -18,7 +18,7 @@
def setUp(self):
uri = os.environ.get('COUCHDB_URI', '
http://localhost:5984/')
- self.server = Server(uri)
+ self.server = Server(uri, full_commit=False)
if 'python-tests' in self.server:
del self.server['python-tests']
self.db = self.server.create('python-tests')
=======================================
--- /branches/experimental/httplib/couchdb/tests/schema.py Tue Jun 30
01:35:19 2009
+++ /branches/experimental/httplib/couchdb/tests/schema.py Tue Sep 8
14:17:41 2009
@@ -18,7 +18,7 @@
def setUp(self):
uri = os.environ.get('COUCHDB_URI', '
http://localhost:5984/')
- self.server = client.Server(uri)
+ self.server = client.Server(uri, full_commit=False)
if 'python-tests' in self.server:
del self.server['python-tests']
self.db = self.server.create('python-tests')
@@ -80,7 +80,7 @@
def setUp(self):
uri = os.environ.get('COUCHDB_URI', '
http://localhost:5984/')
- self.server = client.Server(uri)
+ self.server = client.Server(uri, full_commit=False)
if 'python-tests' in self.server:
del self.server['python-tests']
self.db = self.server.create('python-tests')
=======================================
--- /branches/experimental/httplib/setup.py Thu Jul 2 07:37:29 2009
+++ /branches/experimental/httplib/setup.py Tue Sep 8 14:17:41 2009
@@ -103,7 +103,7 @@
doctest.testfile(filename, False, optionflags=doctest.ELLIPSIS)
-requirements = ['httplib2']
+requirements = []
if sys.version_info < (2, 6):
requirements += ['simplejson']