1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20  r"""Support for automatically documenting an API. 
 21   
 22  """ 
 23  __docformat__ = "restructuredtext en" 
 24   
 25  import inspect 
 26  from decorators import allow_GET, pathinfo 
 27  from wsgisupport import Response, HTTPNotFound 
 28  from application import Resource 
 31      if not hasattr(obj, '_wsgiwapi_props'): 
 32          return {} 
 33      return obj._wsgiwapi_props 
  34   
 36      def build_link_tree(result, urls, leadingcomponents, thiscomponent): 
 37          components = urls.keys() 
 38          components.sort() 
 39          for component in components: 
 40              value = urls[component] 
 41              if component is None: 
 42                  fullcomponents = leadingcomponents 
 43                  componentpath = thiscomponent 
 44              else: 
 45                  fullcomponents = leadingcomponents + (component, ) 
 46                  componentpath = thiscomponent + '/' + component 
 47              result.append('<li><a href="%(component)s">/%(fullpath)s</a>' % 
 48                            {'component': componentpath, 
 49                            'fullpath': '/'.join(fullcomponents)}) 
 50              if hasattr(value, '__call__'): 
 51                  doc = inspect.getdoc(value) 
 52                  if doc is None: 
 53                      doc = '' 
 54                  else: 
 55                      doc = doc.split('\n', 1)[0] 
 56                  result.append(' ') 
 57                  props = get_properties(value) 
 58                  rettype = props.get('return_type', '') 
 59                  if rettype: 
 60                      result.append("[%s] " % rettype) 
 61                  result.append(doc) 
 62              else: 
 63                  result.append('<ul>') 
 64                  build_link_tree(result, value, fullcomponents, componentpath) 
 65                  result.append('</ul>') 
 66              result.append('</li>') 
  67   
 68      def path_description(callable): 
 69          """Generate a description of the subsequent path items. 
 70   
 71          """ 
 72          if isinstance(callable, Resource): 
 73               
 74              args, varargs, varkw, defaults = inspect.getargspec(callable.methods['GET']) 
 75          else: 
 76              args, varargs, varkw, defaults = inspect.getargspec(callable) 
 77   
 78          args = args[1:] 
 79          if len(args) == 0 and varargs is None: 
 80              return "<li>No extra path components allowed.</li>\n" 
 81          result = [] 
 82          for arg in args: 
 83              result.append("<li>%s</li>" % arg) 
 84          if varargs is not None: 
 85              result.append("<li>...</li>") 
 86          return '\n'.join(result) 
 87   
 88      def param_descriptions(constraints): 
 89          """Generate a description of the parameters allowed. 
 90   
 91          """ 
 92          if len(constraints) == 0: 
 93              return "No parameters allowed" 
 94          result = [] 
 95          for name, values in constraints.iteritems(): 
 96              min, max, pattern, compiled_pattern, default, doc = values 
 97              required = (default is None and min > 0) 
 98              desc = [] 
 99              desc += name 
100              if required: 
101                  desc += " (required)" 
102              else: 
103                  desc += " (optional)" 
104   
105              if doc is not None: 
106                  desc += " - " + doc 
107   
108              desc += "<br/>\n" 
109   
110              if min is not None: 
111                  desc += "Minimum occurrences: %d<br/>\n" % min 
112              if max is not None: 
113                  desc += "Maximum occurrences: %d<br/>\n" % max 
114              if pattern is not None: 
115                  desc += "Must match %s<br/>\n" % pattern 
116   
117              result.append("<li>" + ''.join(desc) + "</li>") 
118          return '\n'.join(result) 
119   
120      @allow_GET 
121      @pathinfo(tail=()) 
122      def doc(request): 
123          """Display documentation about the API. 
124   
125          """ 
126          urls = appurls 
127          for arg in request.pathinfo.tail: 
128              if arg not in urls: 
129                  raise HTTPNotFound(request.path) 
130              if hasattr(urls, '__call__'): 
131                  raise HTTPNotFound(request.path) 
132              urls = urls[arg] 
133   
134          if hasattr(urls, '__call__'): 
135              body = '<pre>%s</pre>' % inspect.getdoc(urls) 
136              props = get_properties(urls) 
137              rettype = props.get('return_type', '') 
138              if rettype: 
139                  body += "<b>Return type: %s</b><br/>\n" % rettype 
140              body += "<b>Path items</b>\n<ul>" 
141              body += path_description(urls) 
142              body += "</ul><br/>\n" 
143              constraints = props.get('valid_params', None) 
144              if constraints != None: 
145                  body += "<b>Parameters:</b>\n<ul>" 
146                  body += param_descriptions(constraints) 
147                  body += "</ul><br/>\n" 
148              methods = props.get('allowed_methods', None) 
149              if methods != None: 
150                  methods = list(methods) 
151                  methods.sort() 
152                  body += "<b>Allowed methods:</b><ul>\n<li>" 
153                  body += '</li><li>'.join(methods) 
154                  body += "</li></ul><br/>\n" 
155          else: 
156              body = ['<ul>'] 
157              thiscomponent = base_doc_url 
158              if len(request.pathinfo.tail): 
159                  thiscomponent = request.pathinfo.tail[-1] 
160              build_link_tree(body, urls, request.pathinfo.tail, thiscomponent) 
161              body.append('</ul>') 
162              body = ''.join(body) 
163          return Response( 
164                  """<html><head><title>%(reqpath)s</title></head><body>%(body)s</body></html>""" % 
165                  { 
166                      'reqpath': request.path, 
167                      'body': body, 
168                  }, content_type='text/html' 
169              ) 
170      return doc 
171   
172   
173