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