kinto 19.4.0__py3-none-any.whl → 19.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kinto might be problematic. Click here for more details.

Files changed (45) hide show
  1. kinto/core/__init__.py +3 -3
  2. kinto/core/cornice/__init__.py +93 -0
  3. kinto/core/cornice/cors.py +144 -0
  4. kinto/core/cornice/errors.py +40 -0
  5. kinto/core/cornice/pyramidhook.py +373 -0
  6. kinto/core/cornice/renderer.py +89 -0
  7. kinto/core/cornice/resource.py +205 -0
  8. kinto/core/cornice/service.py +641 -0
  9. kinto/core/cornice/util.py +138 -0
  10. kinto/core/cornice/validators/__init__.py +94 -0
  11. kinto/core/cornice/validators/_colander.py +142 -0
  12. kinto/core/cornice/validators/_marshmallow.py +182 -0
  13. kinto/core/cornice_swagger/__init__.py +92 -0
  14. kinto/core/cornice_swagger/converters/__init__.py +21 -0
  15. kinto/core/cornice_swagger/converters/exceptions.py +6 -0
  16. kinto/core/cornice_swagger/converters/parameters.py +90 -0
  17. kinto/core/cornice_swagger/converters/schema.py +249 -0
  18. kinto/core/cornice_swagger/swagger.py +725 -0
  19. kinto/core/cornice_swagger/templates/index.html +73 -0
  20. kinto/core/cornice_swagger/templates/index_script_template.html +21 -0
  21. kinto/core/cornice_swagger/util.py +42 -0
  22. kinto/core/cornice_swagger/views.py +78 -0
  23. kinto/core/errors.py +6 -4
  24. kinto/core/openapi.py +2 -3
  25. kinto/core/permission/memory.py +3 -2
  26. kinto/core/permission/testing.py +6 -0
  27. kinto/core/resource/viewset.py +1 -1
  28. kinto/core/testing.py +1 -1
  29. kinto/core/utils.py +3 -2
  30. kinto/core/views/batch.py +1 -1
  31. kinto/core/views/openapi.py +1 -1
  32. kinto/plugins/admin/VERSION +1 -1
  33. kinto/plugins/admin/build/VERSION +1 -1
  34. kinto/plugins/admin/build/assets/{index-D8oiN37x.css → index-BdpYyatM.css} +1 -1
  35. kinto/plugins/admin/build/assets/{index-BKIg2XW8.js → index-n-QM_iZE.js} +65 -65
  36. kinto/plugins/admin/build/index.html +2 -2
  37. kinto/plugins/flush.py +1 -1
  38. kinto/plugins/openid/views.py +1 -1
  39. kinto/views/contribute.py +2 -1
  40. {kinto-19.4.0.dist-info → kinto-19.6.0.dist-info}/METADATA +3 -4
  41. {kinto-19.4.0.dist-info → kinto-19.6.0.dist-info}/RECORD +45 -24
  42. {kinto-19.4.0.dist-info → kinto-19.6.0.dist-info}/LICENSE +0 -0
  43. {kinto-19.4.0.dist-info → kinto-19.6.0.dist-info}/WHEEL +0 -0
  44. {kinto-19.4.0.dist-info → kinto-19.6.0.dist-info}/entry_points.txt +0 -0
  45. {kinto-19.4.0.dist-info → kinto-19.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,725 @@
1
+ """Cornice Swagger 2.0 documentor"""
2
+
3
+ import inspect
4
+ import warnings
5
+ from collections import OrderedDict
6
+
7
+ import colander
8
+ from pyramid.threadlocal import get_current_registry
9
+
10
+ from kinto.core.cornice import Service
11
+ from kinto.core.cornice.util import to_list
12
+ from kinto.core.cornice_swagger.converters import (
13
+ ParameterConversionDispatcher as ParameterConverter,
14
+ )
15
+ from kinto.core.cornice_swagger.converters import TypeConversionDispatcher as TypeConverter
16
+ from kinto.core.cornice_swagger.util import body_schema_transformer, merge_dicts, trim
17
+
18
+
19
+ class CorniceSwaggerException(Exception):
20
+ """Raised when cornice services have structural problems to be converted."""
21
+
22
+
23
+ class DefinitionHandler(object):
24
+ """Handles Swagger object definitions provided by cornice as colander schemas."""
25
+
26
+ json_pointer = "#/definitions/"
27
+
28
+ def __init__(self, ref=0, type_converter=TypeConverter()):
29
+ """
30
+ :param ref:
31
+ The depth that should be used by self.ref when calling self.from_schema.
32
+ """
33
+
34
+ self.definition_registry = {}
35
+ self.ref = ref
36
+ self.type_converter = type_converter
37
+
38
+ def from_schema(self, schema_node, base_name=None):
39
+ """
40
+ Creates a Swagger definition from a colander schema.
41
+
42
+ :param schema_node:
43
+ Colander schema to be transformed into a Swagger definition.
44
+ :param base_name:
45
+ Schema alternative title.
46
+
47
+ :rtype: dict
48
+ :returns: Swagger schema.
49
+ """
50
+ return self._ref_recursive(self.type_converter(schema_node), self.ref, base_name)
51
+
52
+ def _ref_recursive(self, schema, depth, base_name=None):
53
+ """
54
+ Dismantle nested swagger schemas into several definitions using JSON pointers.
55
+ Note: This can be dangerous since definition titles must be unique.
56
+
57
+ :param schema:
58
+ Base swagger schema.
59
+ :param depth:
60
+ How many levels of the swagger object schemas should be split into
61
+ swaggger definitions with JSON pointers. Default (0) is no split.
62
+ You may use negative values to split everything.
63
+ :param base_name:
64
+ If schema doesn't have a name, the caller may provide it to be
65
+ used as reference.
66
+
67
+ :rtype: dict
68
+ :returns:
69
+ JSON pointer to the root definition schema,
70
+ or the original definition if depth is zero.
71
+ """
72
+
73
+ if depth == 0:
74
+ return schema
75
+
76
+ if schema["type"] != "object":
77
+ return schema
78
+
79
+ name = base_name or schema["title"]
80
+
81
+ pointer = self.json_pointer + name
82
+ for child_name, child in schema.get("properties", {}).items():
83
+ schema["properties"][child_name] = self._ref_recursive(child, depth - 1)
84
+
85
+ self.definition_registry[name] = schema
86
+
87
+ return {"$ref": pointer}
88
+
89
+
90
+ class ParameterHandler(object):
91
+ """Handles swagger parameter definitions."""
92
+
93
+ json_pointer = "#/parameters/"
94
+
95
+ def __init__(
96
+ self,
97
+ definition_handler=DefinitionHandler(),
98
+ ref=False,
99
+ type_converter=TypeConverter(),
100
+ parameter_converter=ParameterConverter(TypeConverter()),
101
+ ):
102
+ """
103
+ :param definition_handler:
104
+ Callable that handles swagger definition schemas.
105
+ :param ref:
106
+ Specifies the ref value when calling from_xxx methods.
107
+ """
108
+
109
+ self.parameter_registry = {}
110
+
111
+ self.type_converter = type_converter
112
+ self.parameter_converter = parameter_converter
113
+ self.definitions = definition_handler
114
+ self.ref = ref
115
+
116
+ def from_schema(self, schema_node):
117
+ """
118
+ Creates a list of Swagger params from a colander request schema.
119
+
120
+ :param schema_node:
121
+ Request schema to be transformed into Swagger.
122
+ :param validators:
123
+ Validators used in colander with the schema.
124
+
125
+ :rtype: list
126
+ :returns: List of Swagger parameters.
127
+ """
128
+
129
+ params = []
130
+
131
+ for param_schema in schema_node.children:
132
+ location = param_schema.name
133
+ if location == "body":
134
+ name = param_schema.__class__.__name__
135
+ if name == "body":
136
+ name = schema_node.__class__.__name__ + "Body"
137
+ param = self.parameter_converter(location, param_schema)
138
+ param["name"] = name
139
+ if self.ref:
140
+ param = self._ref(param)
141
+ params.append(param)
142
+
143
+ elif location in (("path", "header", "headers", "querystring", "GET")):
144
+ for node_schema in param_schema.children:
145
+ param = self.parameter_converter(location, node_schema)
146
+ if self.ref:
147
+ param = self._ref(param)
148
+ params.append(param)
149
+
150
+ return params
151
+
152
+ def from_path(self, path):
153
+ """
154
+ Create a list of Swagger path params from a cornice service path.
155
+
156
+ :type path: string
157
+ :rtype: list
158
+ """
159
+ path_components = path.split("/")
160
+ param_names = [
161
+ comp[1:-1] for comp in path_components if comp.startswith("{") and comp.endswith("}")
162
+ ]
163
+
164
+ params = []
165
+ for name in param_names:
166
+ param_schema = colander.SchemaNode(colander.String(), name=name)
167
+ param = self.parameter_converter("path", param_schema)
168
+ if self.ref:
169
+ param = self._ref(param)
170
+ params.append(param)
171
+
172
+ return params
173
+
174
+ def _ref(self, param, base_name=None):
175
+ """
176
+ Store a parameter schema and return a reference to it.
177
+
178
+ :param schema:
179
+ Swagger parameter definition.
180
+ :param base_name:
181
+ Name that should be used for the reference.
182
+
183
+ :rtype: dict
184
+ :returns: JSON pointer to the original parameter definition.
185
+ """
186
+
187
+ name = base_name or param.get("title", "") or param.get("name", "")
188
+
189
+ pointer = self.json_pointer + name
190
+ self.parameter_registry[name] = param
191
+
192
+ return {"$ref": pointer}
193
+
194
+
195
+ class ResponseHandler(object):
196
+ """Handles swagger response definitions."""
197
+
198
+ json_pointer = "#/responses/"
199
+
200
+ def __init__(
201
+ self, definition_handler=DefinitionHandler(), type_converter=TypeConverter(), ref=False
202
+ ):
203
+ """
204
+ :param definition_handler:
205
+ Callable that handles swagger definition schemas.
206
+ :param ref:
207
+ Specifies the ref value when calling from_xxx methods.
208
+ """
209
+
210
+ self.response_registry = {}
211
+
212
+ self.type_converter = type_converter
213
+ self.definitions = definition_handler
214
+ self.ref = ref
215
+
216
+ def from_schema_mapping(self, schema_mapping):
217
+ """
218
+ Creates a Swagger response object from a dict of response schemas.
219
+
220
+ :param schema_mapping:
221
+ Dict with entries matching ``{status_code: response_schema}``.
222
+ :rtype: dict
223
+ :returns: Response schema.
224
+ """
225
+
226
+ responses = {}
227
+
228
+ for status, response_schema in schema_mapping.items():
229
+ response = {}
230
+ if response_schema.description:
231
+ response["description"] = response_schema.description
232
+ else:
233
+ raise CorniceSwaggerException("Responses must have a description.")
234
+
235
+ for field_schema in response_schema.children:
236
+ location = field_schema.name
237
+
238
+ if location == "body":
239
+ title = field_schema.__class__.__name__
240
+ if title == "body":
241
+ title = response_schema.__class__.__name__ + "Body"
242
+ field_schema.title = title
243
+ response["schema"] = self.definitions.from_schema(field_schema)
244
+
245
+ elif location in ("header", "headers"):
246
+ header_schema = self.type_converter(field_schema)
247
+ headers = header_schema.get("properties")
248
+ if headers:
249
+ # Response headers doesn't accept titles
250
+ for header in headers.values():
251
+ header.pop("title")
252
+
253
+ response["headers"] = headers
254
+
255
+ pointer = response_schema.__class__.__name__
256
+ if self.ref:
257
+ response = self._ref(response, pointer)
258
+ responses[status] = response
259
+
260
+ return responses
261
+
262
+ def _ref(self, resp, base_name=None):
263
+ """
264
+ Store a response schema and return a reference to it.
265
+
266
+ :param schema:
267
+ Swagger response definition.
268
+ :param base_name:
269
+ Name that should be used for the reference.
270
+
271
+ :rtype: dict
272
+ :returns: JSON pointer to the original response definition.
273
+ """
274
+
275
+ name = base_name or resp.get("title", "") or resp.get("name", "")
276
+
277
+ pointer = self.json_pointer + name
278
+ self.response_registry[name] = resp
279
+
280
+ return {"$ref": pointer}
281
+
282
+
283
+ class CorniceSwagger(object):
284
+ """Handles the creation of a swagger document from a cornice application."""
285
+
286
+ services = []
287
+ """List of cornice services to document. You may use
288
+ `cornice.service.get_services()` to get it."""
289
+
290
+ definitions = DefinitionHandler
291
+ """Default :class:`cornice_swagger.swagger.DefinitionHandler` class to use when
292
+ handling OpenAPI schema definitions from kinto.core.cornice payload schemas."""
293
+
294
+ parameters = ParameterHandler
295
+ """Default :class:`cornice_swagger.swagger.ParameterHandler` class to use when
296
+ handling OpenAPI operation parameters from kinto.core.cornice request schemas."""
297
+
298
+ responses = ResponseHandler
299
+ """Default :class:`cornice_swagger.swagger.ResponseHandler` class to use when
300
+ handling OpenAPI responses from kinto.core.cornice_swagger defined responses."""
301
+
302
+ schema_transformers = [body_schema_transformer]
303
+ """List of request schema transformers that should be applied to a request
304
+ schema to make it comply with a cornice default request schema."""
305
+
306
+ type_converter = TypeConverter
307
+ """Default :class:`cornice_swagger.converters.schema.TypeConversionDispatcher`
308
+ class used for converting colander schema Types to Swagger Types."""
309
+
310
+ parameter_converter = ParameterConverter
311
+ """Default :class:`cornice_swagger.converters.parameters.ParameterConversionDispatcher`
312
+ class used for converting colander/cornice request schemas to Swagger Parameters."""
313
+
314
+ custom_type_converters = {}
315
+ """Mapping for supporting custom types conversion on the default TypeConverter.
316
+ Should map `colander.TypeSchema` to `cornice_swagger.converters.schema.TypeConverter`
317
+ callables."""
318
+
319
+ default_type_converter = None
320
+ """Supplies a default type converter matching the interface of
321
+ `cornice_swagger.converters.schema.TypeConverter` to be used with unknown types."""
322
+
323
+ default_tags = None
324
+ """Provide a default list of tags or a callable that takes a cornice
325
+ service and the method name (e.g GET) and returns a list of Swagger
326
+ tags to be used if not provided by the view."""
327
+
328
+ default_op_ids = None
329
+ """Provide a callable that takes a cornice service and the method name
330
+ (e.g. GET) and returns an operation Id that is used if an operation Id is
331
+ not provided. Each operation Id should be unique."""
332
+
333
+ default_security = None
334
+ """Provide a default list or a callable that takes a cornice service and
335
+ the method name (e.g. GET) and returns a list of OpenAPI security policies."""
336
+
337
+ summary_docstrings = False
338
+ """Enable extracting operation summaries from view docstrings."""
339
+
340
+ ignore_methods = ["HEAD", "OPTIONS"]
341
+ """List of service methods that should NOT be presented on the
342
+ documentation. You may use this to remove methods that are not
343
+ essential on the API documentation. Default ignores HEAD and OPTIONS."""
344
+
345
+ ignore_ctypes = []
346
+ """List of service content-types that should NOT be presented on the
347
+ documentation. You may use this when a Cornice service definition has
348
+ multiple view definitions for a same method, which is not supported on
349
+ OpenAPI 2.0."""
350
+
351
+ api_title = ""
352
+ """Title of the OpenAPI document."""
353
+
354
+ api_version = ""
355
+ """Version of the OpenAPI document."""
356
+
357
+ base_path = "/"
358
+ """Base path of the documented API. Default is "/"."""
359
+
360
+ swagger = {"info": {}}
361
+ """Base OpenAPI document that should be merged with the extracted info
362
+ from the generate call."""
363
+
364
+ def __init__(
365
+ self,
366
+ services=None,
367
+ def_ref_depth=0,
368
+ param_ref=False,
369
+ resp_ref=False,
370
+ pyramid_registry=None,
371
+ ):
372
+ """
373
+ :param services:
374
+ List of cornice services to document. You may use
375
+ cornice.service.get_services() to get it.
376
+ :param def_ref_depth:
377
+ How depth swagger object schemas should be split into
378
+ swaggger definitions with JSON pointers. Default (0) is no split.
379
+ You may use negative values to split everything.
380
+ :param param_ref:
381
+ Defines if swagger parameters should be put inline on the operation
382
+ or on the parameters section and referenced by JSON pointers.
383
+ Default is inline.
384
+ :param resp_ref:
385
+ Defines if swagger responses should be put inline on the operation
386
+ or on the responses section and referenced by JSON pointers.
387
+ Default is inline.
388
+ :param pyramid_registry:
389
+ Pyramid registry, should be passed if you use pyramid routes
390
+ instead of service level paths.
391
+ """
392
+ super(CorniceSwagger, self).__init__()
393
+
394
+ type_converter = self.type_converter(
395
+ self.custom_type_converters, self.default_type_converter
396
+ )
397
+ parameter_converter = self.parameter_converter(type_converter)
398
+ self.pyramid_registry = pyramid_registry
399
+ if services is not None:
400
+ self.services = services
401
+
402
+ # Instantiate handlers
403
+ self.definitions = self.definitions(ref=def_ref_depth, type_converter=type_converter)
404
+ self.parameters = self.parameters(
405
+ self.definitions,
406
+ ref=param_ref,
407
+ type_converter=type_converter,
408
+ parameter_converter=parameter_converter,
409
+ )
410
+ self.responses = self.responses(
411
+ self.definitions, ref=resp_ref, type_converter=type_converter
412
+ )
413
+
414
+ def generate(
415
+ self, title=None, version=None, base_path=None, info=None, swagger=None, **kwargs
416
+ ):
417
+ """Generate a Swagger 2.0 documentation. Keyword arguments may be used
418
+ to provide additional information to build methods as such ignores.
419
+
420
+ :param title:
421
+ The name presented on the swagger document.
422
+ :param version:
423
+ The version of the API presented on the swagger document.
424
+ :param base_path:
425
+ The path that all requests to the API must refer to.
426
+ :param info:
427
+ Swagger info field.
428
+ :param swagger:
429
+ Extra fields that should be provided on the swagger documentation.
430
+
431
+ :rtype: dict
432
+ :returns: Full OpenAPI/Swagger compliant specification for the application.
433
+ """
434
+ title = title or self.api_title
435
+ version = version or self.api_version
436
+ info = info or self.swagger.get("info", {})
437
+ swagger = swagger or self.swagger
438
+ base_path = base_path or self.base_path
439
+
440
+ swagger = swagger.copy()
441
+ info.update(title=title, version=version)
442
+ swagger.update(swagger="2.0", info=info, basePath=base_path)
443
+
444
+ paths, tags = self._build_paths()
445
+
446
+ # Update the provided tags with the extracted ones preserving order
447
+ if tags:
448
+ swagger.setdefault("tags", [])
449
+ tag_names = {t["name"] for t in swagger["tags"]}
450
+ for tag in tags:
451
+ if tag["name"] not in tag_names:
452
+ swagger["tags"].append(tag)
453
+
454
+ # Create/Update swagger sections with extracted values where not provided
455
+ if paths:
456
+ swagger.setdefault("paths", {})
457
+ merge_dicts(swagger["paths"], paths)
458
+
459
+ definitions = self.definitions.definition_registry
460
+ if definitions:
461
+ swagger.setdefault("definitions", {})
462
+ merge_dicts(swagger["definitions"], definitions)
463
+
464
+ parameters = self.parameters.parameter_registry
465
+ if parameters:
466
+ swagger.setdefault("parameters", {})
467
+ merge_dicts(swagger["parameters"], parameters)
468
+
469
+ responses = self.responses.response_registry
470
+ if responses:
471
+ swagger.setdefault("responses", {})
472
+ merge_dicts(swagger["responses"], responses)
473
+
474
+ return swagger
475
+
476
+ def __call__(self, *args, **kwargs):
477
+ """Deprecated alias of `generate`."""
478
+ self.__dict__.update(**kwargs)
479
+
480
+ message = "Calling `CorniceSwagger is deprecated, call `generate` instead"
481
+ warnings.warn(message, DeprecationWarning)
482
+ return self.generate(*args, **kwargs)
483
+
484
+ def _check_tags(self, tags):
485
+ """Check if tags was correctly defined as a list"""
486
+ if not isinstance(tags, list):
487
+ raise CorniceSwaggerException("tags should be a list or callable")
488
+
489
+ def _get_tags(self, current_tags, new_tags):
490
+ tags = list(current_tags)
491
+ for tag in new_tags:
492
+ root_tag = {"name": tag}
493
+ if root_tag not in tags:
494
+ tags.append(root_tag)
495
+ return tags
496
+
497
+ def _build_paths(self):
498
+ """
499
+ Build the Swagger "paths" and "tags" attributes from kinto.core.cornice service
500
+ definitions.
501
+ """
502
+ paths = {}
503
+ tags = []
504
+
505
+ for service in self.services:
506
+ path, path_obj = self._extract_path_from_service(service)
507
+
508
+ service_tags = getattr(service, "tags", [])
509
+ self._check_tags(service_tags)
510
+ tags = self._get_tags(tags, service_tags)
511
+
512
+ for method, view, args in service.definitions:
513
+ if method.lower() in map(str.lower, self.ignore_methods):
514
+ continue
515
+
516
+ op = self._extract_operation_from_view(view, args)
517
+
518
+ if any(ctype in op.get("consumes", []) for ctype in self.ignore_ctypes):
519
+ continue
520
+
521
+ # XXX: Swagger doesn't support different schemas for for a same method
522
+ # with different ctypes as cornice. If this happens, you may ignore one
523
+ # content-type from the documentation otherwise we raise an Exception
524
+ # Related to https://github.com/OAI/OpenAPI-Specification/issues/146
525
+ previous_definition = path_obj.get(method.lower())
526
+ if previous_definition:
527
+ raise CorniceSwaggerException(
528
+ (
529
+ "Swagger doesn't support multiple "
530
+ "views for a same method. You may "
531
+ "ignore one."
532
+ )
533
+ )
534
+
535
+ # If tag not defined and a default tag is provided
536
+ if "tags" not in op and self.default_tags:
537
+ if callable(self.default_tags):
538
+ op["tags"] = self.default_tags(service, method)
539
+ else:
540
+ op["tags"] = self.default_tags
541
+
542
+ op_tags = op.get("tags", [])
543
+ self._check_tags(op_tags)
544
+
545
+ # Add service tags
546
+ if service_tags:
547
+ new_tags = service_tags + op_tags
548
+ op["tags"] = list(OrderedDict.fromkeys(new_tags))
549
+
550
+ # Add method tags to root tags
551
+ tags = self._get_tags(tags, op_tags)
552
+
553
+ # If operation id is not defined and a default generator is provided
554
+ if "operationId" not in op and self.default_op_ids:
555
+ if not callable(self.default_op_ids):
556
+ raise CorniceSwaggerException("default_op_id should be a callable.")
557
+ op["operationId"] = self.default_op_ids(service, method)
558
+
559
+ # If security options not defined and default is provided
560
+ if "security" not in op and self.default_security:
561
+ if callable(self.default_security):
562
+ op["security"] = self.default_security(service, method)
563
+ else:
564
+ op["security"] = self.default_security
565
+
566
+ if not isinstance(op.get("security", []), list):
567
+ raise CorniceSwaggerException("security should be a list or callable")
568
+
569
+ path_obj[method.lower()] = op
570
+ paths[path] = path_obj
571
+
572
+ return paths, tags
573
+
574
+ def _extract_path_from_service(self, service):
575
+ """
576
+ Extract path object and its parameters from service definitions.
577
+
578
+ :param service:
579
+ Cornice service to extract information from.
580
+
581
+ :rtype: dict
582
+ :returns: Path definition.
583
+ """
584
+
585
+ path_obj = {}
586
+ path = service.path
587
+ route_name = getattr(service, "pyramid_route", None)
588
+ # handle services that don't create fresh routes,
589
+ # we still need the paths so we need to grab pyramid introspector to
590
+ # extract that information
591
+ if route_name:
592
+ # avoid failure if someone forgets to pass registry
593
+ registry = self.pyramid_registry or get_current_registry()
594
+ route_intr = registry.introspector.get("routes", route_name)
595
+ if route_intr:
596
+ path = route_intr["pattern"]
597
+ else:
598
+ msg = "Route `{}` is not found by pyramid introspector".format(route_name)
599
+ raise ValueError(msg)
600
+
601
+ # handle traverse and subpath as regular parameters
602
+ # docs.pylonsproject.org/projects/pyramid/en/latest/narr/hybrid.html
603
+ for subpath_marker in ("*subpath", "*traverse"):
604
+ path = path.replace(subpath_marker, "{subpath}")
605
+
606
+ # Extract path parameters
607
+ parameters = self.parameters.from_path(path)
608
+ if parameters:
609
+ path_obj["parameters"] = parameters
610
+
611
+ return path, path_obj
612
+
613
+ def _extract_operation_from_view(self, view, args):
614
+ """
615
+ Extract swagger operation details from colander view definitions.
616
+
617
+ :param view:
618
+ View to extract information from.
619
+ :param args:
620
+ Arguments from the view decorator.
621
+
622
+ :rtype: dict
623
+ :returns: Operation definition.
624
+ """
625
+
626
+ op = {
627
+ "responses": {"default": {"description": "UNDOCUMENTED RESPONSE"}},
628
+ }
629
+
630
+ # If 'produces' are not defined in the view, try get from renderers
631
+ renderer = args.get("renderer", "")
632
+
633
+ is_json_renderer = (
634
+ "json" in renderer # allows for "json" or "simplejson"
635
+ or renderer == Service.renderer # default renderer is json.
636
+ )
637
+
638
+ if is_json_renderer:
639
+ produces = ["application/json"]
640
+ elif renderer == "xml":
641
+ produces = ["text/xml"]
642
+ else:
643
+ produces = None
644
+
645
+ if produces:
646
+ op.setdefault("produces", produces)
647
+
648
+ # Get explicit accepted content-types
649
+ consumes = args.get("content_type")
650
+
651
+ if consumes is not None:
652
+ # convert to a list, if it's not yet one
653
+ consumes = to_list(consumes)
654
+
655
+ # It is possible to add callables for content_type, so we have to
656
+ # to filter those out, since we cannot evaluate those here.
657
+ consumes = [x for x in consumes if not callable(x)]
658
+ op["consumes"] = consumes
659
+
660
+ # Get parameters from view schema
661
+ is_colander = self._is_colander_schema(args)
662
+ if is_colander:
663
+ schema = self._extract_transform_colander_schema(args)
664
+ parameters = self.parameters.from_schema(schema)
665
+ else:
666
+ # Bail out for now
667
+ parameters = None
668
+ if parameters:
669
+ op["parameters"] = parameters
670
+
671
+ # Get summary from docstring
672
+ if isinstance(view, str):
673
+ if "klass" in args:
674
+ ob = args["klass"]
675
+ view_ = getattr(ob, view.lower())
676
+ docstring = trim(view_.__doc__)
677
+ else:
678
+ docstring = str(trim(view.__doc__))
679
+
680
+ if docstring and self.summary_docstrings:
681
+ op["summary"] = docstring
682
+
683
+ # Get response definitions
684
+ if "response_schemas" in args:
685
+ op["responses"] = self.responses.from_schema_mapping(args["response_schemas"])
686
+
687
+ # Get response tags
688
+ if "tags" in args:
689
+ op["tags"] = args["tags"]
690
+
691
+ # Get response operationId
692
+ if "operation_id" in args:
693
+ op["operationId"] = args["operation_id"]
694
+
695
+ # Get security policies
696
+ if "api_security" in args:
697
+ op["security"] = args["api_security"]
698
+
699
+ return op
700
+
701
+ def _is_colander_schema(self, args):
702
+ schema = args.get("schema")
703
+ return isinstance(schema, colander.Schema) or (
704
+ inspect.isclass(schema) and issubclass(schema, colander.MappingSchema)
705
+ )
706
+
707
+ def _extract_transform_colander_schema(self, args):
708
+ """
709
+ Extract schema from view args and transform it using
710
+ the pipeline of schema transformers
711
+
712
+ :param args:
713
+ Arguments from the view decorator.
714
+
715
+ :rtype: colander.MappingSchema()
716
+ :returns: View schema cloned and transformed
717
+ """
718
+
719
+ schema = args.get("schema", colander.MappingSchema())
720
+ if not isinstance(schema, colander.Schema):
721
+ schema = schema()
722
+ schema = schema.clone()
723
+ for transformer in self.schema_transformers:
724
+ schema = transformer(schema, args)
725
+ return schema