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