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 
 27  from wsgisupport import Response, HTTPNotFound 
 30      if not hasattr(obj, '_wsgiwebapi_props'): 
 31          return default 
 32      return obj._wsgiwebapi_props.get(prop, default) 
  33   
 35      def build_link_tree(result, urls, leadingcomponents, thiscomponent): 
 36          components = urls.keys() 
 37          components.sort() 
 38          for component in components: 
 39              value = urls[component] 
 40              fullcomponents = leadingcomponents + (component, ) 
 41              componentpath = thiscomponent + '/' + component 
 42              result.append('<li><a href="%(component)s">/%(fullpath)s</a>' % 
 43                            {'component': componentpath, 
 44                            'fullpath': '/'.join(fullcomponents)}) 
 45              if hasattr(value, '__call__'): 
 46                  doc = inspect.getdoc(value) 
 47                  if doc is None: 
 48                      doc = '' 
 49                  else: 
 50                      doc = doc.partition('\n')[0] 
 51                  result.append(' ') 
 52                  rettype = get_property(value, 'return_type', '') 
 53                  if rettype: 
 54                      result.append("[%s] " % rettype) 
 55                  result.append(doc) 
 56              else: 
 57                  result.append('<ul>') 
 58                  build_link_tree(result, value, fullcomponents, componentpath) 
 59                  result.append('</ul>') 
 60              result.append('</li>') 
  61   
 62      def path_description(callable): 
 63          """Generate a description of the subsequent path items. 
 64   
 65          """ 
 66          args, varargs, varkw, defaults = inspect.getargspec(callable) 
 67          args = args[1:] 
 68          if len(args) == 0 and varargs is None: 
 69              return "<li>No extra path components allowed.</li>\n" 
 70          result = [] 
 71          for arg in args: 
 72              result.append("<li>%s</li>" % arg) 
 73          if varargs is not None: 
 74              result.append("<li>...</li>") 
 75          return '\n'.join(result) 
 76   
 77      def param_descriptions(constraints): 
 78          """Generate a description of the parameters allowed. 
 79   
 80          """ 
 81          if len(constraints) == 0: 
 82              return "No parameters allowed" 
 83          result = [] 
 84          for name, values in constraints.iteritems(): 
 85              min, max, pattern, default, doc = values 
 86              required = (default is None and min > 0) 
 87              desc = [] 
 88              desc += name 
 89              if required: 
 90                  desc += " (required)" 
 91              else: 
 92                  desc += " (optional)" 
 93   
 94              if doc is not None: 
 95                  desc += " - " + doc 
 96   
 97              desc += "<br/>\n" 
 98   
 99              if min is not None: 
100                  desc += "Minimum occurrences: %d<br/>\n" % min 
101              if max is not None: 
102                  desc += "Maximum occurrences: %d<br/>\n" % max 
103              if pattern is not None: 
104                  desc += "Must match %s<br/>\n" % pattern 
105   
106              result.append("<li>" + ''.join(desc) + "</li>") 
107          return '\n'.join(result) 
108   
109      @allow_GET 
110      def doc(request, *args): 
111          """Display documentation about the API. 
112   
113          """ 
114          urls = appurls 
115          for arg in args: 
116              if arg not in urls: 
117                  raise HTTPNotFound(request.path) 
118              if hasattr(urls, '__call__'): 
119                  raise HTTPNotFound(request.path) 
120              urls = urls[arg] 
121   
122          if hasattr(urls, '__call__'): 
123              body = '<pre>%s</pre>' % inspect.getdoc(urls) 
124              rettype = get_property(urls, 'return_type', '') 
125              if rettype: 
126                  body += "<b>Return type: %s</b><br/>\n" % rettype 
127              body += "<b>Path items</b>\n<ul>" 
128              body += path_description(urls) 
129              body += "</ul><br/>\n" 
130              constraints = get_property(urls, 'valid_params', None) 
131              if constraints != None: 
132                  body += "<b>Parameters:</b>\n<ul>" 
133                  body += param_descriptions(constraints) 
134                  body += "</ul><br/>\n" 
135              methods = get_property(urls, 'allowed_methods', None) 
136              if methods != None: 
137                  methods = list(methods) 
138                  methods.sort() 
139                  body += "<b>Allowed methods:</b><ul>\n<li>" 
140                  body += '</li><li>'.join(methods) 
141                  body += "</li></ul><br/>\n" 
142          else: 
143              body = ['<ul>'] 
144              thiscomponent = 'doc' 
145              if len(args): 
146                  thiscomponent = args[-1] 
147              build_link_tree(body, urls, args, thiscomponent) 
148              body.append('</ul>') 
149              body = ''.join(body) 
150          return Response( 
151                  """<html><head><title>%(reqpath)s</title></head><body>%(body)s</body></html>""" % 
152                  { 
153                      'reqpath': request.path, 
154                      'body': body, 
155                  }, content_type='text/html' 
156              ) 
157      return doc 
158   
159   
160