Dodanie omyłkowo pominiętego API.
[wolnelektury.git] / apps / piston / doc.py
diff --git a/apps/piston/doc.py b/apps/piston/doc.py
new file mode 100644 (file)
index 0000000..63f89ec
--- /dev/null
@@ -0,0 +1,195 @@
+import inspect, handler
+
+from piston.handler import typemapper
+from piston.handler import handler_tracker
+
+from django.core.urlresolvers import get_resolver, get_callable, get_script_prefix
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+def generate_doc(handler_cls):
+    """
+    Returns a `HandlerDocumentation` object
+    for the given handler. Use this to generate
+    documentation for your API.
+    """
+    if not type(handler_cls) is handler.HandlerMetaClass:
+        raise ValueError("Give me handler, not %s" % type(handler_cls))
+        
+    return HandlerDocumentation(handler_cls)
+    
+class HandlerMethod(object):
+    def __init__(self, method, stale=False):
+        self.method = method
+        self.stale = stale
+        
+    def iter_args(self):
+        args, _, _, defaults = inspect.getargspec(self.method)
+
+        for idx, arg in enumerate(args):
+            if arg in ('self', 'request', 'form'):
+                continue
+
+            didx = len(args)-idx
+
+            if defaults and len(defaults) >= didx:
+                yield (arg, str(defaults[-didx]))
+            else:
+                yield (arg, None)
+        
+    @property
+    def signature(self, parse_optional=True):
+        spec = ""
+
+        for argn, argdef in self.iter_args():
+            spec += argn
+            
+            if argdef:
+                spec += '=%s' % argdef
+            
+            spec += ', '
+            
+        spec = spec.rstrip(", ")
+        
+        if parse_optional:
+            return spec.replace("=None", "=<optional>")
+            
+        return spec
+        
+    @property
+    def doc(self):
+        return inspect.getdoc(self.method)
+    
+    @property
+    def name(self):
+        return self.method.__name__
+    
+    @property
+    def http_name(self):
+        if self.name == 'read':
+            return 'GET'
+        elif self.name == 'create':
+            return 'POST'
+        elif self.name == 'delete':
+            return 'DELETE'
+        elif self.name == 'update':
+            return 'PUT'
+    
+    def __repr__(self):
+        return "<Method: %s>" % self.name
+    
+class HandlerDocumentation(object):
+    def __init__(self, handler):
+        self.handler = handler
+        
+    def get_methods(self, include_default=False):
+        for method in "read create update delete".split():
+            met = getattr(self.handler, method, None)
+
+            if not met:
+                continue
+                
+            stale = inspect.getmodule(met) is handler
+
+            if not self.handler.is_anonymous:
+                if met and (not stale or include_default):
+                    yield HandlerMethod(met, stale)
+            else:
+                if not stale or met.__name__ == "read" \
+                    and 'GET' in self.allowed_methods:
+                    
+                    yield HandlerMethod(met, stale)
+        
+    def get_all_methods(self):
+        return self.get_methods(include_default=True)
+        
+    @property
+    def is_anonymous(self):
+        return handler.is_anonymous
+
+    def get_model(self):
+        return getattr(self, 'model', None)
+            
+    @property
+    def has_anonymous(self):
+        return self.handler.anonymous
+            
+    @property
+    def anonymous(self):
+        if self.has_anonymous:
+            return HandlerDocumentation(self.handler.anonymous)
+            
+    @property
+    def doc(self):
+        return self.handler.__doc__
+    
+    @property
+    def name(self):
+        return self.handler.__name__
+    
+    @property
+    def allowed_methods(self):
+        return self.handler.allowed_methods
+    
+    def get_resource_uri_template(self):
+        """
+        URI template processor.
+        
+        See http://bitworking.org/projects/URI-Templates/
+        """
+        def _convert(template, params=[]):
+            """URI template converter"""
+            paths = template % dict([p, "{%s}" % p] for p in params)
+            return u'%s%s' % (get_script_prefix(), paths)
+        
+        try:
+            resource_uri = self.handler.resource_uri()
+            
+            components = [None, [], {}]
+
+            for i, value in enumerate(resource_uri):
+                components[i] = value
+        
+            lookup_view, args, kwargs = components
+            lookup_view = get_callable(lookup_view, True)
+
+            possibilities = get_resolver(None).reverse_dict.getlist(lookup_view)
+            
+            for possibility, pattern in possibilities:
+                for result, params in possibility:
+                    if args:
+                        if len(args) != len(params):
+                            continue
+                        return _convert(result, params)
+                    else:
+                        if set(kwargs.keys()) != set(params):
+                            continue
+                        return _convert(result, params)
+        except:
+            return None
+        
+    resource_uri_template = property(get_resource_uri_template)
+    
+    def __repr__(self):
+        return u'<Documentation for "%s">' % self.name
+
+def documentation_view(request):
+    """
+    Generic documentation view. Generates documentation
+    from the handlers you've defined.
+    """
+    docs = [ ]
+
+    for handler in handler_tracker: 
+        docs.append(generate_doc(handler))
+
+    def _compare(doc1, doc2): 
+       #handlers and their anonymous counterparts are put next to each other.
+       name1 = doc1.name.replace("Anonymous", "")
+       name2 = doc2.name.replace("Anonymous", "")
+       return cmp(name1, name2)    
+    docs.sort(_compare)
+       
+    return render_to_response('documentation.html', 
+        { 'docs': docs }, RequestContext(request))