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