changed tag description display
[wolnelektury.git] / apps / piston / doc.py
1 import inspect, handler
2
3 from piston.handler import typemapper
4 from piston.handler import handler_tracker
5
6 from django.core.urlresolvers import get_resolver, get_callable, get_script_prefix
7 from django.shortcuts import render_to_response
8 from django.template import RequestContext
9
10 def generate_doc(handler_cls):
11     """
12     Returns a `HandlerDocumentation` object
13     for the given handler. Use this to generate
14     documentation for your API.
15     """
16     if not type(handler_cls) is handler.HandlerMetaClass:
17         raise ValueError("Give me handler, not %s" % type(handler_cls))
18         
19     return HandlerDocumentation(handler_cls)
20     
21 class HandlerMethod(object):
22     def __init__(self, method, stale=False):
23         self.method = method
24         self.stale = stale
25         
26     def iter_args(self):
27         args, _, _, defaults = inspect.getargspec(self.method)
28
29         for idx, arg in enumerate(args):
30             if arg in ('self', 'request', 'form'):
31                 continue
32
33             didx = len(args)-idx
34
35             if defaults and len(defaults) >= didx:
36                 yield (arg, str(defaults[-didx]))
37             else:
38                 yield (arg, None)
39         
40     @property
41     def signature(self, parse_optional=True):
42         spec = ""
43
44         for argn, argdef in self.iter_args():
45             spec += argn
46             
47             if argdef:
48                 spec += '=%s' % argdef
49             
50             spec += ', '
51             
52         spec = spec.rstrip(", ")
53         
54         if parse_optional:
55             return spec.replace("=None", "=<optional>")
56             
57         return spec
58         
59     @property
60     def doc(self):
61         return inspect.getdoc(self.method)
62     
63     @property
64     def name(self):
65         return self.method.__name__
66     
67     @property
68     def http_name(self):
69         if self.name == 'read':
70             return 'GET'
71         elif self.name == 'create':
72             return 'POST'
73         elif self.name == 'delete':
74             return 'DELETE'
75         elif self.name == 'update':
76             return 'PUT'
77     
78     def __repr__(self):
79         return "<Method: %s>" % self.name
80     
81 class HandlerDocumentation(object):
82     def __init__(self, handler):
83         self.handler = handler
84         
85     def get_methods(self, include_default=False):
86         for method in "read create update delete".split():
87             met = getattr(self.handler, method, None)
88
89             if not met:
90                 continue
91                 
92             stale = inspect.getmodule(met) is handler
93
94             if not self.handler.is_anonymous:
95                 if met and (not stale or include_default):
96                     yield HandlerMethod(met, stale)
97             else:
98                 if not stale or met.__name__ == "read" \
99                     and 'GET' in self.allowed_methods:
100                     
101                     yield HandlerMethod(met, stale)
102         
103     def get_all_methods(self):
104         return self.get_methods(include_default=True)
105         
106     @property
107     def is_anonymous(self):
108         return handler.is_anonymous
109
110     def get_model(self):
111         return getattr(self, 'model', None)
112             
113     @property
114     def has_anonymous(self):
115         return self.handler.anonymous
116             
117     @property
118     def anonymous(self):
119         if self.has_anonymous:
120             return HandlerDocumentation(self.handler.anonymous)
121             
122     @property
123     def doc(self):
124         return self.handler.__doc__
125     
126     @property
127     def name(self):
128         return self.handler.__name__
129     
130     @property
131     def allowed_methods(self):
132         return self.handler.allowed_methods
133     
134     def get_resource_uri_template(self):
135         """
136         URI template processor.
137         
138         See http://bitworking.org/projects/URI-Templates/
139         """
140         def _convert(template, params=[]):
141             """URI template converter"""
142             paths = template % dict([p, "{%s}" % p] for p in params)
143             return u'%s%s' % (get_script_prefix(), paths)
144         
145         try:
146             resource_uri = self.handler.resource_uri()
147             
148             components = [None, [], {}]
149
150             for i, value in enumerate(resource_uri):
151                 components[i] = value
152         
153             lookup_view, args, kwargs = components
154             lookup_view = get_callable(lookup_view, True)
155
156             possibilities = get_resolver(None).reverse_dict.getlist(lookup_view)
157             
158             for possibility, pattern in possibilities:
159                 for result, params in possibility:
160                     if args:
161                         if len(args) != len(params):
162                             continue
163                         return _convert(result, params)
164                     else:
165                         if set(kwargs.keys()) != set(params):
166                             continue
167                         return _convert(result, params)
168         except:
169             return None
170         
171     resource_uri_template = property(get_resource_uri_template)
172     
173     def __repr__(self):
174         return u'<Documentation for "%s">' % self.name
175
176 def documentation_view(request):
177     """
178     Generic documentation view. Generates documentation
179     from the handlers you've defined.
180     """
181     docs = [ ]
182
183     for handler in handler_tracker: 
184         docs.append(generate_doc(handler))
185
186     def _compare(doc1, doc2): 
187        #handlers and their anonymous counterparts are put next to each other.
188        name1 = doc1.name.replace("Anonymous", "")
189        name2 = doc2.name.replace("Anonymous", "")
190        return cmp(name1, name2)    
191  
192     docs.sort(_compare)
193        
194     return render_to_response('documentation.html', 
195         { 'docs': docs }, RequestContext(request))