Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2012-01-14 17:15:43 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-01-14 17:15:43 (GMT)
commita04cc2ed342a0eaa59221332c94979a7ab54e9c3 (patch)
tree0660bc092afc6864574d4dc6016149b95c4ffa28
parent4bfdc7335d0ebcdda04d43632b522ff4dca5bb24 (diff)
Since rd is eventlet based, move request/responce objects to env module
-rw-r--r--restful_document/__init__.py2
-rw-r--r--restful_document/document.py32
-rw-r--r--restful_document/env.py127
-rw-r--r--restful_document/metadata.py46
-rw-r--r--restful_document/router.py66
-rw-r--r--tests/__init__.py32
-rwxr-xr-xtests/units/document.py44
7 files changed, 214 insertions, 135 deletions
diff --git a/restful_document/__init__.py b/restful_document/__init__.py
index 3a3770e..9c09d54 100644
--- a/restful_document/__init__.py
+++ b/restful_document/__init__.py
@@ -15,4 +15,4 @@
from restful_document.document import Document, restful_method
from restful_document.metadata import Metadata, Method
-from restful_document.router import Router, Responce
+from restful_document.router import Router
diff --git a/restful_document/document.py b/restful_document/document.py
index 395b69e..792f80e 100644
--- a/restful_document/document.py
+++ b/restful_document/document.py
@@ -18,31 +18,22 @@ from gettext import gettext as _
import active_document as ad
from restful_document import env
+from restful_document.metadata import restful_method
from restful_document.util import enforce
-def restful_method(**kwargs):
-
- def decorate(func):
- func.is_restful_method = True
- func.restful_cls_kwargs = kwargs
- return func
-
- return decorate
-
-
class Document(ad.Document):
"""All RESTful document classes need to inherit this one."""
@classmethod
@restful_method(method='POST')
- def _restful_create(cls, response):
- doc = cls.create(response.request)
+ def _restful_create(cls):
+ doc = cls.create(env.request.content)
return {'guid': doc.guid}
@classmethod
@restful_method(method='GET')
- def _restful_find(cls, response, **kwargs):
+ def _restful_find(cls, **kwargs):
offset = env.pop_int('offset', kwargs, None)
limit = env.pop_int('limit', kwargs, None)
query = env.pop_str('query', kwargs, None)
@@ -55,23 +46,24 @@ class Document(ad.Document):
'documents': [i.all_properties(reply) for i in documents]}
@restful_method(method='PUT')
- def _restful_update(self, response, prop=None):
+ def _restful_update(self, prop=None):
if prop is None:
- self.update(self.guid, response.request)
+ self.update(self.guid, env.request.content)
elif isinstance(self.metadata[prop], ad.BlobProperty):
- self.set_blob(prop, response.rfile, response.rsize)
+ self.set_blob(prop, env.request.content_stream,
+ env.request.content_length)
else:
- self[prop] = response.request
+ self[prop] = env.request.content
self.post()
@restful_method(method='DELETE')
- def _restful_delete(self, response, prop=None):
+ def _restful_delete(self, prop=None):
enforce(prop is None, env.Forbidden,
_('Properties cannot be deleted'))
self.delete(self.guid)
@restful_method(method='GET')
- def _restful_get(self, response, prop=None):
+ def _restful_get(self, prop=None):
if prop is None:
reply = []
for name, prop in self.metadata.items():
@@ -79,7 +71,7 @@ class Document(ad.Document):
reply.append(name)
return self.all_properties(reply)
elif isinstance(self.metadata[prop], ad.BlobProperty):
- response.headers['Content-Type'] = self.metadata[prop].mime_type
+ env.responce['Content-Type'] = self.metadata[prop].mime_type
return self.get_blob(prop)
else:
return self[prop]
diff --git a/restful_document/env.py b/restful_document/env.py
index 1d4adca..d0ba7b8 100644
--- a/restful_document/env.py
+++ b/restful_document/env.py
@@ -13,6 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import json
+import threading
+from urlparse import parse_qsl
from gettext import gettext as _
from restful_document.util import enforce
@@ -21,21 +24,6 @@ from restful_document.util import enforce
_default = object()
-class HTTPError(Exception):
-
- status = '500 Internal Server Error'
-
-
-class Forbidden(HTTPError):
-
- status = '403 Forbidden'
-
-
-class BadRequest(HTTPError):
-
- status = '400 Bad Request'
-
-
def pop_str(name, kwargs, default=_default):
if name in kwargs:
return kwargs.pop(name)
@@ -61,3 +49,112 @@ def pop_list(name, kwargs, default=_default):
# pylint: disable-msg=E1103
value = value.split(',')
return value
+
+
+class HTTPError(Exception):
+
+ status = '500 Internal Server Error'
+ headers = {}
+
+
+class Forbidden(HTTPError):
+
+ status = '403 Forbidden'
+ headers = {}
+
+
+class BadRequest(HTTPError):
+
+ status = '400 Bad Request'
+ headers = {}
+
+
+class Unauthorized(HTTPError):
+
+ status = '401 Unauthorized'
+ headers = {'WWW-Authenticate': 'Sugar'}
+
+
+class Request(threading.local):
+
+ environ = None
+ method = None
+ url = None
+ path = None
+ query = None
+ content = None
+ content_stream = None
+ content_length = None
+
+ def set(self, environ):
+ self.environ = environ
+
+ self.method = environ['REQUEST_METHOD']
+ self.url = environ['PATH_INFO'] or '/'
+ self.path = \
+ [i for i in environ['PATH_INFO'].strip('/').split('/') if i]
+ self.query = {}
+ self.content = None
+ self.content_stream = environ.get('wsgi.input')
+ self.content_length = 0
+
+ for attr, value in parse_qsl(environ.get('QUERY_STRING', '')):
+ self.query[str(attr)] = value
+
+ if self.query:
+ self.url += '?' + environ.get('QUERY_STRING')
+
+ content_length = environ.get('CONTENT_LENGTH')
+ if content_length:
+ self.content_length = int(content_length)
+ if environ.get('CONTENT_TYPE', '').lower() == 'application/json':
+ content = self.read()
+ if content:
+ self.content = json.loads(content)
+
+ def read(self, size=None):
+ if size is None:
+ size = self.content_length
+ if not size:
+ return ''
+ result = self.content_stream.read(size)
+ self.content_length -= len(result)
+ return result
+
+ def __getitem__(self, key):
+ return self.environ.get('HTTP_%s' % key)
+
+ def __contains__(self, key):
+ return ('HTTP_%s' % key) in self.environ
+
+
+class Responce(threading.local):
+
+ status = None
+ _headers = None
+
+ def set(self):
+ self.status = '200 OK'
+ self._headers = {}
+
+ def update(self, values):
+ self._headers.update(values)
+
+ def items(self):
+ return self._headers.items()
+
+ def setdefault(self, key, value=None):
+ self._headers.setdefault(key, value)
+
+ def __getitem__(self, key):
+ return self._headers.get(key)
+
+ def __setitem__(self, key, value):
+ self._headers[key] = value
+
+ def __contains__(self, key):
+ return key in self._headers
+
+
+request = Request()
+responce = Responce()
diff --git a/restful_document/metadata.py b/restful_document/metadata.py
index 5791aab..066d32c 100644
--- a/restful_document/metadata.py
+++ b/restful_document/metadata.py
@@ -20,6 +20,16 @@ from restful_document import util, env
from restful_document.util import enforce
+def restful_method(**kwargs):
+
+ def decorate(func):
+ func.is_restful_method = True
+ func.restful_cls_kwargs = kwargs
+ return func
+
+ return decorate
+
+
class Metadata(object):
def __init__(self, classes):
@@ -31,27 +41,31 @@ class Metadata(object):
enforce(meth.cmd not in methods,
_('Method %s already exists in %s'),
meth.cmd, methods.get(meth.cmd))
- methods[meth.name] = meth
+ methods[meth.cmd] = meth
else:
enforce(meth.method not in methods,
_('%s method already exists in %s'),
meth.method, methods.get(meth.method))
methods[meth.method] = meth
- def get_method(self, response):
- enforce(len(response.path) <= 3, env.BadRequest,
+ def get_method(self):
+ enforce(len(env.request.path) <= 3, env.BadRequest,
_('Requested path consists of more than three parts'))
- if len(response.path) == 3:
- response.query['prop'] = response.path.pop()
+ if len(env.request.path) == 3:
+ env.request.query['prop'] = env.request.path.pop()
- scope = len(response.path)
+ scope = len(env.request.path)
if scope == 0:
document = None
else:
- document = response.path[0]
+ document = env.request.path[0]
enforce(document in self._methods[scope], env.BadRequest,
_('Unknown document type, %s'), document)
- method_name = response.query.get('cmd') or response.request_method
+
+ if 'cmd' in env.request.query:
+ method_name = env.request.query.pop('cmd')
+ else:
+ method_name = env.request.method
return self._methods[scope][document].get(method_name)
@@ -71,25 +85,25 @@ class Method(object):
def __str__(self):
return str(self._cb)
- def __call__(self, response):
+ def __call__(self):
try:
- result = self._call(response)
+ result = self._call()
except TypeError:
util.exception()
raise env.BadRequest(_('Inappropriate arguments'))
- response.headers.setdefault('Content-Type', self.mime_type)
+ env.responce.setdefault('Content-Type', self.mime_type)
return result
- def _call(self, response):
- return self._cb(response, **response.query)
+ def _call(self):
+ return self._cb(**env.request.query)
class _ObjectMethod(Method):
- def _call(self, response):
- guid = response.path[1]
+ def _call(self):
+ guid = env.request.path[1]
doc = self.cls(guid)
- return self._cb(doc, response, **response.query)
+ return self._cb(doc, **env.request.query)
def _list_methods(classes):
diff --git a/restful_document/router.py b/restful_document/router.py
index e361736..f1cd360 100644
--- a/restful_document/router.py
+++ b/restful_document/router.py
@@ -15,7 +15,6 @@
import json
import types
-import urlparse
from gettext import gettext as _
import active_document as ad
@@ -32,62 +31,35 @@ class Router(object):
self.metadata = Metadata(classes)
def __call__(self, environ, start_response):
- response = Responce(environ)
+ env.request.set(environ)
+ env.responce.set()
+
try:
- method = self.metadata.get_method(response)
+ method = self.metadata.get_method()
enforce(method is not None and \
- method.method == response.request_method, env.BadRequest,
+ method.method == env.request.method, env.BadRequest,
_('No way to handle the request'))
- if environ.get('CONTENT_TYPE', '').lower() == 'application/json':
- request = response.read()
- if request:
- response.request = json.loads(request)
- result = method(response)
+ result = method()
+
except Exception, error:
util.exception(_('Error while processing %s request'),
- response.request_url)
- if isinstance(error, env.HTTPError):
- response.status = error.status
+ env.request.url)
+ if isinstance(error, ad.Unauthorized):
+ env.responce.status = env.Unauthorized.status
+ env.responce.update(env.Unauthorized.headers)
+ elif isinstance(error, env.HTTPError):
+ env.responce.status = error.status
+ env.responce.update(error.headers)
else:
- response.status = '500 Internal Server Error'
- response.headers['Content-Type'] = 'application/json'
- result = {'error': str(error), 'request': response.request_url}
+ env.responce.status = '500 Internal Server Error'
+ env.responce['Content-Type'] = 'application/json'
+ result = {'error': str(error), 'request': env.request.url}
- start_response(response.status, response.headers.items())
+ start_response(env.responce.status, env.responce.items())
if isinstance(result, types.GeneratorType):
for i in result:
yield i
else:
- if response.headers['Content-Type'] == 'application/json':
+ if env.responce['Content-Type'] == 'application/json':
result = json.dumps(result)
yield result
-
-
-class Responce(object):
-
- def __init__(self, environ):
- self.status = '200 OK'
- self.headers = {}
- self.query = dict(urlparse.parse_qsl(environ.get('QUERY_STRING', '')))
- self.path = \
- [i for i in environ['PATH_INFO'].strip('/').split('/') if i]
- self.request_method = environ['REQUEST_METHOD']
- self.request = None
- self.request_url = environ['PATH_INFO'] or '/'
- if self.query:
- self.request_url += '?' + environ.get('QUERY_STRING')
- self.rfile = environ.get('wsgi.input')
- self.rsize = 0
-
- rsize = environ.get('CONTENT_LENGTH')
- if rsize:
- self.rsize = int(rsize)
-
- def read(self, size=None):
- if size is None:
- size = self.rsize
- if not size:
- return ''
- result = self.rfile.read(size)
- self.rsize -= len(result)
- return result
diff --git a/tests/__init__.py b/tests/__init__.py
index 292590b..010c8c7 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -14,6 +14,7 @@ from os.path import dirname, join, exists, abspath
import gobject
import dbus.glib
import dbus.mainloop.glib
+import restkit
import active_document as ad
import restful_document as rd
@@ -136,3 +137,34 @@ class Test(unittest.TestCase):
del Test.httpd_pids[port]
os.kill(pid, signal.SIGTERM)
os.waitpid(pid, 0)
+
+
+class Resource(restkit.Resource):
+
+ def get(self, path=None, headers=None, **kwargs):
+ if headers is None:
+ headers = {}
+ if 'Content-Type' not in headers:
+ headers['Content-Type'] = 'application/json'
+ return restkit.Resource.get(self, path, headers=headers, **kwargs)
+
+ def put(self, path=None, headers=None, **kwargs):
+ if headers is None:
+ headers = {}
+ if 'Content-Type' not in headers:
+ headers['Content-Type'] = 'application/json'
+ return restkit.Resource.put(self, path, headers=headers, **kwargs)
+
+ def post(self, path=None, headers=None, **kwargs):
+ if headers is None:
+ headers = {}
+ if 'Content-Type' not in headers:
+ headers['Content-Type'] = 'application/json'
+ return restkit.Resource.post(self, path, headers=headers, **kwargs)
+
+ def delete(self, path=None, headers=None, **kwargs):
+ if headers is None:
+ headers = {}
+ if 'Content-Type' not in headers:
+ headers['Content-Type'] = 'application/json'
+ return restkit.Resource.delete(self, path, headers=headers, **kwargs)
diff --git a/tests/units/document.py b/tests/units/document.py
index 6c6be72..9e6cc91 100755
--- a/tests/units/document.py
+++ b/tests/units/document.py
@@ -2,10 +2,10 @@
# sugar-lint: disable
import json
-
-import restkit
+import time
from __init__ import tests
+from tests import Resource
import active_document as ad
import restful_document as rd
@@ -147,6 +147,8 @@ class DocumentTest(tests.Test):
json.loads(rest.get('/document', reply='guid,stored,term,vote,counter').body_string()))
def test_ServerCrash(self):
+ ad.index_write_queue.value = 10
+
self.httpd(8000, [Document])
rest = Resource('http://localhost:8000')
@@ -165,14 +167,15 @@ class DocumentTest(tests.Test):
)['guid']
- reply = json.loads(rest.get('/document', reply='guid,stored,term,vote,counter').body_string())
+ reply = json.loads(rest.get('/document', reply='guid,stored,term,vote').body_string())
self.assertEqual(2, reply['total'])
self.assertEqual(
- sorted([{'guid': guid_1, 'stored': 'stored', 'term': 'term', 'vote': '0', 'counter': '0'},
- {'guid': guid_2, 'stored': 'stored2', 'term': 'term2', 'vote': '1', 'counter': '1'},
+ sorted([{'guid': guid_1, 'stored': 'stored', 'term': 'term', 'vote': '0'},
+ {'guid': guid_2, 'stored': 'stored2', 'term': 'term2', 'vote': '1'},
]),
sorted(reply['documents']))
+ time.sleep(1)
self.httpdown(8000)
self.httpd(8000, [Document])
@@ -185,36 +188,5 @@ class DocumentTest(tests.Test):
sorted(reply['documents']))
-class Resource(restkit.Resource):
-
- def get(self, path=None, headers=None, **kwargs):
- if headers is None:
- headers = {}
- if 'Content-Type' not in headers:
- headers['Content-Type'] = 'application/json'
- return restkit.Resource.get(self, path, headers=headers, **kwargs)
-
- def put(self, path=None, headers=None, **kwargs):
- if headers is None:
- headers = {}
- if 'Content-Type' not in headers:
- headers['Content-Type'] = 'application/json'
- return restkit.Resource.put(self, path, headers=headers, **kwargs)
-
- def post(self, path=None, headers=None, **kwargs):
- if headers is None:
- headers = {}
- if 'Content-Type' not in headers:
- headers['Content-Type'] = 'application/json'
- return restkit.Resource.post(self, path, headers=headers, **kwargs)
-
- def delete(self, path=None, headers=None, **kwargs):
- if headers is None:
- headers = {}
- if 'Content-Type' not in headers:
- headers['Content-Type'] = 'application/json'
- return restkit.Resource.delete(self, path, headers=headers, **kwargs)
-
-
if __name__ == '__main__':
tests.main()