def test_merge_simple_dicts_with_precedence(self): a = {'a': 'value', 'ab': 'overwritten'} b = {'b': 'other value', 'ab': 'keep'} assert utils.merge(a, b) == { 'a': 'value', 'b': 'other value', 'ab': 'keep' }
def wrapper(func): doc = { 'responses': { code: (description, [fields]) if as_list else ( description, fields) }, '__mask__': kwargs.get('mask', True), } func.__apidoc__ = merge(getattr(func, '__apidoc__', {}), doc) return marshal_with(fields, ordered=self.ordered, **kwargs)(func)
def wrapper(func): doc = { 'responses': { code: (description, [fields]) if as_list else (description, fields) }, '__mask__': kwargs.get('mask', True), # Mask values can't be determined outside app context } func.__apidoc__ = merge(getattr(func, '__apidoc__', {}), doc) kwargs['strict'] = strict return marshal_with(fields, **kwargs)(func)
def add_response_doc(func, fields, as_list, code=200, description=None, **kwargs): doc = { 'responses': { code: (description, [fields]) if as_list else (description, fields) }, '__mask__': kwargs.get('mask', True), # Mask values can't be determined outside app context } func.__apidoc__ = merge(getattr(func, '__apidoc__', {}), doc) return func
def _handle_api_doc(self, cls, doc): if doc is False: cls.__apidoc__ = False return ##unshortcut_params_description(doc) ##for key in 'get', 'post', 'put', 'delete', 'options', 'head', 'patch': ## if key in doc: ## if doc[key] is False: ## continue ## unshortcut_params_description(doc[key]) cls.__apidoc__ = merge(getattr(cls, '__apidoc__', {}), doc)
def test_recursions_with_empty(self): a = {} b = { 'b': 'other value', 'ab': 'keep', 'nested_b': { 'b': 'nested' }, 'nested_a_b': { 'b': 'b only', 'ab': 'keep' } } self.assertEqual(utils.merge(a, b), b)
def _handle_api_doc(self, cls, doc): if doc is False: cls.__apidoc__ = False return ##unshortcut_params_description(doc) ##handle_deprecations(doc) ##for key in 'get', 'post', 'put', 'delete', 'options', 'head', 'patch': ## if key in doc: ## if doc[key] is False: ## continue ## unshortcut_params_description(doc[key]) ## handle_deprecations(doc[key]) ## if 'expect' in doc[key] and not isinstance(doc[key]['expect'], (list, tuple)): ## doc[key]['expect'] = [doc[key]['expect']] cls.__apidoc__ = merge(getattr(cls, '__apidoc__', {}), doc)
def test_recursions(self): a = { 'a': 'value', 'ab': 'overwritten', 'nested_a': { 'a': 'nested' }, 'nested_a_b': { 'a': 'a only', 'ab': 'overwritten' } } b = { 'b': 'other value', 'ab': 'keep', 'nested_b': { 'b': 'nested' }, 'nested_a_b': { 'b': 'b only', 'ab': 'keep' } } self.assertEqual(utils.merge(a, b), { 'a': 'value', 'b': 'other value', 'ab': 'keep', 'nested_a': { 'a': 'nested' }, 'nested_b': { 'b': 'nested' }, 'nested_a_b': { 'a': 'a only', 'b': 'b only', 'ab': 'keep' } })
def _handle_api_doc(self, cls, doc): if doc is False: cls.__apidoc__ = False return cls.__apidoc__ = merge(getattr(cls, '__apidoc__', {}), doc)
def doc_endpoint( fn_name, fn_args, schema=None, schema_view=None, description=None, search_parsers=None, search_params_doc=None, parser=None, params=None, permissions=None, is_collection=None, **kwargs): """ Generate a Swagger 2.0 compliant documentation dict that describes an API endpoint operation and its parameters. :param fn_name: The name of the `APIResource`'s method that implements this operation. The naming of this method is used to derive the container type of the return value. :param fn_args: A list of the parameter names for the `APIResource`'s method. :param schema: The `APISchema` class representing the Swagger response model. :param schema_view: The name of the schema view for this `schema`, optional. It's combined with the `schema`'s name to form the name of the Swagger model. :param description: A description of this API operation that will be shown as "Implementation Notes", optional. :param search_parsers: One or more search related parsers describing the operation's input parameters, optional. :param search_params_doc: A documentation dict describing the components of a search query string, optional. :param parser: The parser describing the operation's input parameters, optional. :param params: An explicit list of Swagger documentation input params to include alongside the others. :param permissions: One or more `Permission`s describing the `Need`s of this operation, optional. See `doc_response_permissions`. :param is_collection: The Swagger response is represented as a collection of models if True or as a single model if False. The default is `None` which means the collection type will be derived from `fn_name`. :param kwargs: A dict of additional information to merge into the final documentation dict. :return: The final documentation dict """ params_list = [] parser_list = [] schema_model = None if schema: # Resolve the schema model name, taking the schema view into account. schema_model = _resolve_schema_name(schema, schema_view) # Determine if the response is a single model or a collection of them # If a collection, the name of the schema model will put into a list so that Swagger knows its an array. if is_collection is not None: schema_model = [schema_model] if is_collection else schema_model elif '_many' in fn_name or '_all' in fn_name: schema_model = [schema_model] # Accumulate the common params for this resource endpoint if 'id' in fn_args: params_list.append(doc_param_id()) if search.SearchConfig.FIELD_SELECTORS_PARAM in fn_args: params_list.append(doc_param_selectors()) if search_params_doc: params_list.append(doc_param_search_query(search_params_doc=search_params_doc)) if params: params_list.append(params) # Accumulate the common parsers for this resource endpoint if search_parsers: parser_list.extend(search_parsers) if parser: parser_list.append(parser) # Resolve all parser params and combine them with the others to create a single params dict params = _resolve_params(params_list=params_list, parser_list=parser_list) # Build the final documentation dict doc = dict(model=schema_model) if schema_model else dict() # doc['id'] = fn_name # Use the function's name as the operation's identifier (this value is also the "action") if params: doc['params'] = params if description: doc['description'] = description doc.update(kwargs) if permissions: doc['permissions'] = doc_permissions(*permissions) # Recursively merge the response permissions into the documentation dict doc = merge(doc, doc_response_permissions(*permissions)) return doc
def merge_apidoc(from_field, to_field): if hasattr(from_field, '__apidoc__'): to_field.__apidoc__ = merge(getattr(to_field, '__apidoc__', {}), from_field.__apidoc__)
def create_restplus_field_from_marshmallow_field(field, _nested_field_name=None): """Create a Restplus field from a Marshmallow field. The Restplus field contains the API documentation and metadata necessary to generate the Swagger documentation. :param field: A Marshmallow :class:`Field` instance. :param _nested_field_name: A derived name for the nested field -- for internal use only. :return: A Restplus field derived from its base `Raw` class. """ def field_kwargs(f, *attrs): kwargs = {} for attr in attrs: if isinstance(attr, tuple): a, k = attr else: a = k = attr try: value = getattr(f, a) except AttributeError: value = f.metadata.get(a) kwargs[k] = value return kwargs def merge_apidoc(from_field, to_field): if hasattr(from_field, '__apidoc__'): to_field.__apidoc__ = merge(getattr(to_field, '__apidoc__', {}), from_field.__apidoc__) if not hasattr(field, '__apidoc__'): field.__apidoc__ = {} restplus_field = None restplus_cls = None restplus_attrs = ['attribute', 'default', 'description', 'required'] # default attrs if isinstance(field, ma.Arbitrary): restplus_cls = restplus_fields.Arbitrary restplus_attrs += ['min', 'max'] elif isinstance(field, (ma.ExternalId, ma.Html)): restplus_cls = restplus_fields.String elif isinstance(field, (ma.Select, ma.Enum)): restplus_cls = restplus_fields.String restplus_attrs += [('choices', 'enum')] elif isinstance(field, ma.Fixed): restplus_cls = restplus_fields.Fixed restplus_attrs += ('min', 'max', 'decimals') elif isinstance(field, ma.Float): restplus_cls = restplus_fields.Float restplus_attrs += ['min', 'max'] elif isinstance(field, ma.FormattedString): restplus_cls = restplus_fields.FormattedString restplus_attrs += ['min', 'max', 'src_str'] elif isinstance(field, ma.Integer): restplus_cls = restplus_fields.Integer restplus_attrs += ['min', 'max'] elif isinstance(field, ma.List): container_field = field.container merge_apidoc(field, container_field) restplus_field = restplus_fields.List( create_restplus_field_from_marshmallow_field(container_field), **field_kwargs(field, *restplus_attrs)) elif isinstance(field, ma.Nested): nested = field.nested # Marshmallow supports self-referential nested fields by using the `self` identifier. if nested == 'self': # In this case, the nested field's schema class will be the same as its parent class nested = field.parent.__class__ _nested_field_name = _nested_field_name or nested.resolve_schema_name() # Support Swagger array type documentation field.__apidoc__ = merge(field.__apidoc__, {'as_list': field.many}) nested_proxy = APIModelProxy(_nested_field_name or field.schema_name) restplus_cls = restplus_fields.Nested restplus_attrs = ('attribute', 'default', 'allow_null') # set attrs explicitly restplus_field = restplus_fields.Nested(nested=nested_proxy, **field_kwargs(field, *restplus_attrs)) elif isinstance(field, ma.Raw): restplus_cls = restplus_fields.Raw elif isinstance(field, ma.Url): restplus_cls = restplus_fields.Url restplus_attrs += ('endpoint', 'absolute', 'scheme') elif isinstance(field, (ma.Function, ma.Method)): if field.return_field is not None: return_field = field.return_field merge_apidoc(field, return_field) _nested_field_name = field.return_field.schema.resolve_schema_name(field.schema_view) \ if isinstance(field.return_field, ma.Nested) else None return create_restplus_field_from_marshmallow_field(return_field, _nested_field_name) restplus_cls = restplus_fields.Raw else: try: field_name = field.__class__.__name__ restplus_cls = getattr(restplus_fields, field_name) except AttributeError: # logger.warn( # "Unable to convert Marshmallow field to Restplus field. Unsupported type: {}".format(field_name)) restplus_cls = restplus_fields.String if restplus_field is None: restplus_field = restplus_cls(**field_kwargs(field, *restplus_attrs)) if restplus_cls else None # Denote deprecated fields if 'deprecated' in field.metadata: field.__apidoc__['deprecated'] = field.metadata['deprecated'] # Include additional documentation metadata based on field type if isinstance(field, ma.Enum): field.__apidoc__['enum_cls'] = field.enum_cls elif isinstance(field, ma.Html): field.__apidoc__['has_html'] = True # Copy Swagger documentation over to restplus field if hasattr(field, '__apidoc__'): restplus_field.__apidoc__ = field.__apidoc__ return restplus_field
def test_merge_simple_dicts_without_precedence(self): a = {'a': 'value'} b = {'b': 'other value'} self.assertEqual(utils.merge(a, b), {'a': 'value', 'b': 'other value'})
def test_merge_simple_dicts_with_precedence(self): a = {'a': 'value', 'ab': 'overwritten'} b = {'b': 'other value', 'ab': 'keep'} self.assertEqual(utils.merge(a, b), {'a': 'value', 'b': 'other value', 'ab': 'keep'})