update projects in scenario
[releng.git] / utils / test / testapi / opnfv_testapi / tornado_swagger / swagger.py
1 ##############################################################################
2 # Copyright (c) 2016 ZTE Corporation
3 # feng.xiaowei@zte.com.cn
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9 from HTMLParser import HTMLParser
10 from functools import wraps
11 import inspect
12
13 import epydoc.markup
14 import tornado.web
15
16 from opnfv_testapi.tornado_swagger import handlers
17 from opnfv_testapi.tornado_swagger import settings
18
19
20 class EpytextParser(HTMLParser):
21     a_text = False
22
23     def __init__(self, tag):
24         HTMLParser.__init__(self)
25         self.tag = tag
26         self.data = None
27
28     def handle_starttag(self, tag, attr):
29         if tag == self.tag:
30             self.a_text = True
31
32     def handle_endtag(self, tag):
33         if tag == self.tag:
34             self.a_text = False
35
36     def handle_data(self, data):
37         if self.a_text:
38             self.data = data
39
40     def get_data(self):
41         return self.data
42
43
44 class DocParser(object):
45     def __init__(self):
46         self.notes = None
47         self.summary = None
48         self.responseClass = None
49         self.responseMessages = []
50         self.params = {}
51         self.properties = {}
52
53     def parse_docstring(self, text):
54         if text is None:
55             return
56
57         errors = []
58         doc = epydoc.markup.parse(text, markup='epytext', errors=errors)
59         _, fields = doc.split_fields(errors)
60
61         for field in fields:
62             tag = field.tag()
63             arg = field.arg()
64             body = field.body()
65             self._get_parser(tag)(arg=arg, body=body)
66         return doc
67
68     def _get_parser(self, tag):
69         parser = {
70             'param': self._parse_param,
71             'type': self._parse_type,
72             'in': self._parse_in,
73             'required': self._parse_required,
74             'rtype': self._parse_rtype,
75             'property': self._parse_property,
76             'ptype': self._parse_ptype,
77             'return': self._parse_return,
78             'raise': self._parse_return,
79             'notes': self._parse_notes,
80             'description': self._parse_description,
81         }
82         return parser.get(tag, self._not_supported)
83
84     def _parse_param(self, **kwargs):
85         arg = kwargs.get('arg', None)
86         body = self._get_body(**kwargs)
87         self.params.setdefault(arg, {}).update({
88             'name': arg,
89             'description': body,
90         })
91
92         if 'paramType' not in self.params[arg]:
93             self.params[arg]['paramType'] = 'query'
94
95     def _parse_type(self, **kwargs):
96         arg = kwargs.get('arg', None)
97         code = self._parse_epytext_para('code', **kwargs)
98         link = self._parse_epytext_para('link', **kwargs)
99         if code is None:
100             self.params.setdefault(arg, {}).update({
101                 'name': arg,
102                 'type': link
103             })
104         elif code == 'list':
105             self.params.setdefault(arg, {}).update({
106                 'type': 'array',
107                 'items': {'type': link}
108             })
109
110     def _parse_in(self, **kwargs):
111         arg = kwargs.get('arg', None)
112         body = self._get_body(**kwargs)
113         self.params.setdefault(arg, {}).update({
114             'name': arg,
115             'paramType': body
116         })
117
118     def _parse_required(self, **kwargs):
119         arg = kwargs.get('arg', None)
120         body = self._get_body(**kwargs)
121         self.params.setdefault(arg, {}).update({
122             'name': arg,
123             'required': False if body in ['False', 'false'] else True
124         })
125
126     def _parse_rtype(self, **kwargs):
127         body = self._get_body(**kwargs)
128         self.responseClass = body
129
130     def _parse_property(self, **kwargs):
131         arg = kwargs.get('arg', None)
132         self.properties.setdefault(arg, {}).update({
133             'type': 'string'
134         })
135
136     def _parse_ptype(self, **kwargs):
137         arg = kwargs.get('arg', None)
138         code = self._parse_epytext_para('code', **kwargs)
139         link = self._parse_epytext_para('link', **kwargs)
140         if code is None:
141             self.properties.setdefault(arg, {}).update({
142                 'type': link
143             })
144         elif code == 'list':
145             self.properties.setdefault(arg, {}).update({
146                 'type': 'array',
147                 'items': {'type': link}
148             })
149
150     def _parse_return(self, **kwargs):
151         arg = kwargs.get('arg', None)
152         body = self._get_body(**kwargs)
153         self.responseMessages.append({
154             'code': arg,
155             'message': body
156         })
157
158     def _parse_notes(self, **kwargs):
159         body = self._get_body(**kwargs)
160         self.notes = self._sanitize_doc(body)
161
162     def _parse_description(self, **kwargs):
163         body = self._get_body(**kwargs)
164         self.summary = self._sanitize_doc(body)
165
166     def _not_supported(self, **kwargs):
167         pass
168
169     @staticmethod
170     def _sanitize_doc(comment):
171         return comment.replace('\n', '<br/>') if comment else comment
172
173     @staticmethod
174     def _get_body(**kwargs):
175         body = kwargs.get('body', None)
176         return body.to_plaintext(None).strip() if body else body
177
178     @staticmethod
179     def _parse_epytext_para(tag, **kwargs):
180         def _parse_epytext(tag, body):
181             epytextParser = EpytextParser(tag)
182             epytextParser.feed(str(body))
183             data = epytextParser.get_data()
184             epytextParser.close()
185             return data
186
187         body = kwargs.get('body', None)
188         return _parse_epytext(tag, body) if body else body
189
190
191 class model(DocParser):
192     def __init__(self, *args, **kwargs):
193         super(model, self).__init__()
194         self.args = args
195         self.kwargs = kwargs
196         self.required = []
197         self.cls = None
198
199     def __call__(self, *args):
200         if self.cls:
201             return self.cls
202
203         cls = args[0]
204         self._parse_model(cls)
205
206         return cls
207
208     def _parse_model(self, cls):
209         self.id = cls.__name__
210         self.cls = cls
211         if '__init__' in dir(cls):
212             self._parse_args(cls.__init__)
213         self.parse_docstring(inspect.getdoc(cls))
214         settings.models.append(self)
215
216     def _parse_args(self, func):
217         argspec = inspect.getargspec(func)
218         argspec.args.remove("self")
219         defaults = {}
220         if argspec.defaults:
221             defaults = list(zip(argspec.args[-len(argspec.defaults):],
222                                 argspec.defaults))
223         required_args_count = len(argspec.args) - len(defaults)
224         for arg in argspec.args[:required_args_count]:
225             self.required.append(arg)
226             self.properties.setdefault(arg, {'type': 'string'})
227         for arg, default in defaults:
228             self.properties.setdefault(arg, {
229                 'type': 'string',
230                 "default": default
231             })
232
233
234 class operation(DocParser):
235     def __init__(self, nickname='apis', **kwds):
236         super(operation, self).__init__()
237         self.nickname = nickname
238         self.func = None
239         self.func_args = []
240         self.kwds = kwds
241
242     def __call__(self, *args, **kwds):
243         if self.func:
244             return self.func(*args, **kwds)
245
246         func = args[0]
247         self._parse_operation(func)
248
249         @wraps(func)
250         def __wrapper__(*in_args, **in_kwds):
251             return self.func(*in_args, **in_kwds)
252
253         __wrapper__.rest_api = self
254         return __wrapper__
255
256     def _parse_operation(self, func):
257         self.func = func
258
259         self.__name__ = func.__name__
260         self._parse_args(func)
261         self.parse_docstring(inspect.getdoc(self.func))
262
263     def _parse_args(self, func):
264         argspec = inspect.getargspec(func)
265         argspec.args.remove("self")
266
267         defaults = []
268         if argspec.defaults:
269             defaults = argspec.args[-len(argspec.defaults):]
270
271         for arg in argspec.args:
272             if arg in defaults:
273                 required = False
274             else:
275                 required = True
276             self.params.setdefault(arg, {
277                 'name': arg,
278                 'required': required,
279                 'paramType': 'path',
280                 'dataType': 'string'
281             })
282         self.func_args = argspec.args
283
284
285 def docs(**opts):
286     settings.docs_settings.update(opts)
287
288
289 class Application(tornado.web.Application):
290     def __init__(self, app_handlers=None,
291                  default_host="",
292                  transforms=None,
293                  **settings):
294         super(Application, self).__init__(
295             handlers.swagger_handlers() + app_handlers,
296             default_host,
297             transforms,
298             **settings)