Commits

J...@Blacky  committed 7aa333c

rausgezogen aus https://bitbucket.org/janbrohl/pyscoutnet in rev 70

umbenannt weil name in pypi nicht mehr frei...

  • Participants

Comments (0)

Files changed (8)

+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>simplerest</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.python.pydev.PyDevBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.python.pydev.pythonNature</nature>
+	</natures>
+</projectDescription>

File .pydevproject

+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?><pydev_project>
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
+<path>/simplerest/src</path>
+</pydev_pathproperty>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
+</pydev_project>

File .settings/org.eclipse.core.resources.prefs

+eclipse.preferences.version=1
+encoding//src/simplerest/rest.py=utf-8
+encoding//src/simplerest/testserver.py=utf-8
+encoding//src/simplerest/util.py=utf-8
+Copyright (c) 2012, Jan Brohl <janbrohl@t-online.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File src/simplerest/__init__.py

Empty file added.

File src/simplerest/rest.py

+# -*- coding: utf-8 -*-
+'''
+Created on 22.06.2012
+
+@author: Jan Brohl <janbrohl@t-online.de>
+@license: Simplified BSD License see license.txt
+@copyright: Copyright (c) 2012, Jan Brohl <janbrohl@t-online.de>. All rights reserved.
+'''
+from urlparse import urljoin
+from urllib import quote, urlencode
+from simplerest.util import geturl, default_hook, CallableVirtualPath
+try:
+    from json import dumps, loads, JSONDecoder
+except:
+    from simplejson  import dumps, loads, JSONDecoder
+
+
+class REST(object):
+    def __init__(self, basispfad, media_types=["*/*"]):
+        self.basispfad = basispfad
+        self.media_types = media_types
+
+    def get(self, objektpfad, **query_args):
+        """
+        abrufen eines objektpfades und deserialisieren der antwort
+        """
+        if query_args:
+            objektpfad = objektpfad + "?" + urlencode(query_args)
+        s = geturl(urljoin(self.basispfad, objektpfad),
+                   headers={"Accept": ", ".join(self.media_types)})
+        obj = self.load(s)
+        if isinstance(obj, Exception):
+            raise obj
+        return obj
+
+    def virtual_path(self):
+        return CallableVirtualPath(oncall=(lambda path, query_args:
+                                   self.get("/".join(path), **query_args)))
+
+
+class JSON_REST(REST):
+    def __init__(self, basispfad, object_hook=default_hook):
+        REST.__init__(self, basispfad, ["application/json"])
+        self.object_hook = object_hook
+
+    def load(self, s):
+        return loads(s, object_hook=self.object_hook)
+
+    def get(self, objektpfad, **query_args):
+        return REST.get(self, objektpfad,
+                        **dict((k, dumps(v)) for k, v in query_args.items()))

File src/simplerest/testserver.py

+# -*- coding: utf-8 -*-
+'''
+Created on 23.06.2012
+
+@warning: almost untested
+
+@author: Jan Brohl <janbrohl@t-online.de>
+@license: Simplified BSD License see license.txt
+@copyright: Copyright (c) 2012, Jan Brohl <janbrohl@t-online.de>. All rights reserved.
+'''
+
+from wsgiref.simple_server import make_server
+from wsgiref.util import request_uri
+from json import dumps, load
+from urlparse import urlsplit, parse_qs
+statuscodes = {200:"OK", 404:"Not Found", 500:"Internal Server Error", 415:"Unsupported Media Type", 201:"Created", 204:"No Content", 501:"Not Implemented"}
+
+
+def rc(code):
+    return "%i %s" % (code, statuscodes[code])
+
+class App(object):
+    def __init__(self, root):
+        self.root = root
+
+    def find(self, path):
+        obj = self.root
+        for p in path:
+            try:
+                obj = obj[p]
+            except:
+                try:
+                    obj = obj[int(p)]
+                except:
+                    raise KeyError(p)
+
+    def response_obj(self, start_response, code, parsed, obj):
+        try:
+            ret = dumps(obj)
+        except:
+            return self.response_text(start_response, 500, '"%s" could not serialized' % parsed.geturl())
+        headers = [("Content-Type", "application/json; charset=UTF-8"), ("content-length", str(len(ret)))]
+        start_response(rc(code), headers)
+        return [ret]
+
+    def response_empty(self, start_response, code, headers=[]):
+        start_response(rc(code), headers)
+        return []
+
+    def response_text(self, start_response, code, text):
+        start_response(rc(code), [("Content-Type", "text/plain; charset=UTF-8"), ("content-length", str(len(text)))])
+        return [text]
+
+    def __call__(self, environ, start_response):
+        parsed = urlsplit(request_uri(environ))
+        url = parsed.path.strip("/")
+        #query = parse_qs(environ["QUERY_STRING"], keep_blank_values=True)
+        if url:
+            path = url.split("/")
+        else:
+            path = []
+        method = environ["REQUEST_METHOD"]
+        if method == "GET" or method == "HEAD":
+            try:
+                obj = self.find(path)
+            except KeyError, e:
+                self.response_text(415, '"%s" could not be mapped to an object' % parsed.geturl())
+            self.response_obj(start_response, 200, parsed, obj)
+        elif method == "DELETE":
+            obj = self.find(path[:-1])
+            obj = obj.pop(path[-1])
+            self.response_obj(start_response, 200, parsed, obj)
+        elif method == "PUT":
+            if not environ["HTTP_Content-Type"].startswith("application/json"):
+                return self.response_text(start_response, 415, 'only "application/json" supported for put')
+            value = load(environ["wsgi.input"])
+            try:
+                obj = self.find(path[:-1])
+            except KeyError, e:
+                self.response_text(415, '"%s" could not be mapped to an object' % parsed.geturl())
+            name = path[-1]
+            if name in obj:
+                obj[name] = value
+                return self.response_empty(start_response, 204)
+            else:
+                obj[name] = value
+                return self.response_empty(start_response, 201)
+        else:
+            self.response_text(start_response, 501, 'Sorry, "%s" is not yet implemented.' % method)
+
+
+def make_app_server(rootobj, host="", port=80):
+    return make_server(host, port, App(rootobj))

File src/simplerest/util.py

+# -*- coding: utf-8 -*-
+'''
+Created on 16.06.2012
+
+@author: Jan Brohl <janbrohl@t-online.de>
+@license: Simplified BSD License see license.txt
+@copyright: Copyright (c) 2012, Jan Brohl <janbrohl@t-online.de>. All rights reserved.
+'''
+from httplib import HTTPSConnection, HTTPConnection
+from urlparse import urlsplit, urlunsplit
+import re
+import datetime
+import codecs
+from UserDict import DictMixin
+datere = re.compile(u"([0-9]{1,4})-([01]?[0-9])-([0-3]?[0-9])$")
+timere = re.compile(u"([0-2]?[0-9]):([0-6]?[0-9]):([0-6]?[0-9])$")
+datetimere = re.compile(u"([0-9]{1,4})-([01]?[0-9])-([0-3]?[0-9]).([0-2]?[0-9]):([0-6]?[0-9]):([0-6]?[0-9])$")
+intre = re.compile(u"[+-]?0|(?:[1-9][0-9]*)$")
+floatre = re.compile(u"((?:[+-]?0|(?:[1-9][0-9]*))?)[.,]([0-9]+)(?:[Ee]|(?:[*]10[^])([+-]?0|(?:[1-9][0-9]*)))?$")  #TODO: mehr formate
+numstartre = re.compile(u"[+-]?[.]?[0-9]")
+type_charsetre = re.compile('(?P<maintype>\\S+?)/(?P<subtype>\\S+?)(?:;\\s*charset=(?P<charset>\\S+))')
+
+accept = dict()
+try:
+    accept["deflate"] = codecs.getdecoder("zlib")
+except LookupError:
+    pass
+try:
+    accept["bz2"] = codecs.getdecoder("bz2")
+except LookupError:
+    pass
+default_headers = {"Accept-Encoding":", ".join(accept.keys()), "User-Agent":"PyREST/0.1"}
+
+
+def request(url, method="GET", body=None, headers=dict()):
+    """
+    connect to an url and get a response-object
+    """
+    s = urlsplit(url)
+    host = s.netloc
+    if s.scheme == "https":
+        conn = HTTPSConnection(host)
+    else:
+        conn = HTTPConnection(host)
+    h = dict(default_headers)
+    h.update(headers)
+    if body is not None:
+        conn.request(method, urlunsplit(("", "", s[2], s[3], s[4])), body, h)
+    else:
+        conn.request(method, urlunsplit(("", "", s[2], s[3], s[4])), headers=h)
+    return conn.getresponse()
+
+
+def decode(response):
+    """
+    Read response and decode response-body to unicode
+    """
+    length = response.getheader("content-length", None)
+    encoding = response.getheader("Content-Encoding", None)
+    m = type_charsetre.match(response.getheader("Content-Type"))
+    charset = m.group("charset")
+    if length is not None:
+        s = response.read(int(length))
+    else:
+        s = response.read()
+    if encoding in accept:
+        s = accept[encoding](s)[0]
+    return s.decode(charset)
+
+
+def geturl(url, headers=dict()):
+    """
+    Read Unicode from URL
+    """
+    return decode(request(url, headers=headers))
+
+
+def convert(s):
+    """
+    Convert String value if possible in a safe way
+    """
+    if isinstance(s, basestring) and numstartre.match(s) is not None:
+        m = datetimere.match(s)
+        if m is not None:
+            return datetime.datetime(*_to_ints(m))
+        m = datere.match(s)
+        if m is not None:
+            return datetime.date(*_to_ints(m))
+        m = timere.match(s)
+        if m is not None:
+            return datetime.time(*_to_ints(m))
+        m = intre.match(s)
+        if m is not None:
+            return int(m.group())
+        m = floatre.match(s)
+        if m is not None:
+            if len(m.groups()) == 3:
+                return float("%s.%se%s" % tuple((g or "0") for g in m.groups()))
+            return float("%s.%s" % tuple((g or "0") for g in m.groups()))
+    return s
+
+
+def default_hook(obj):
+    """
+    for use as object_hook in json.loads
+    
+    """
+    stuff = dict()
+    for k, v in obj.items():
+        if v is not None:
+            if isinstance(v, basestring):
+                v = convert(v)
+            elif isinstance(v, dict):
+                v = default_hook(v)
+        stuff[convert(k)] = v
+    return stuff
+
+
+def _to_ints(m):
+    """
+    Convert all groups of a match to int
+    """
+    return (int(g.lstrip("0") or "0") for g in m.groups())
+
+
+def as_list(mapping):
+    """
+    Convert dict to list if possible (if mapping.keys()==range(len(mapping)))
+    else return dict
+    """
+    try:
+        return list(mapping[i] for i in range(len(mapping)))
+    except (KeyError, IndexError):
+        return mapping
+
+
+class VirtualPath(DictMixin):
+    def __init__(self, path=[], **kwargs):
+        self.__path = path
+        self.__kwargs = kwargs
+
+    def __getitem__(self, key):
+        return self.__class__(path=self.__path + [key], **self.__kwargs)
+
+    def __str__(self):
+        return str(self())
+
+    @property
+    def path_as_list(self):
+        return list(self.__path)
+
+    def __eq__(self, other):
+        return (isinstance(other, self.__class__)
+                and all(other.kwarg(k) == v for k, v in self.__kwargs.items())
+                and self.path_as_list == other.path_as_list
+                and other == self)
+
+    def kwarg(self, name):
+        return self.__kwargs.get(name, None)
+
+    def __repr__(self):
+        return "%s(%r, %s)" % (self.__class__.__name__,
+                               self.path_as_list,
+                               ", ".join("%r=%r" % kv for kv in self.__kwargs.items()))
+
+class CallableVirtualPath(VirtualPath):
+    def __init__(self, path=[], oncall=(lambda path, kwargs:
+                                        "/".join(path)), **kwargs):
+        VirtualPath.__init__(self, path, oncall=oncall, **kwargs)
+
+    def __call__(self, **kwargs):
+        self.__kwargs["oncall"](self.__path, kwargs)