Example #1
0
 def path_helper(self, operations, view, **kwargs):
     """Path helper that allows passing a Sanic view function."""
     operations_from_docs = load_operations_from_docstring(view.__doc__)
     # Operation is specified in OpenAPI specifications
     # Refer: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object
     if operations_from_docs:
         operations.update(operations_from_docs)
     for method in view.view_class.methods:
         method_name = method.lower()
         method = getattr(view.view_class, method_name)
         method_operation = load_yaml_from_docstring(method.__doc__)
         if method_operation:
             operations[method_name] = load_yaml_from_docstring(
                 method.__doc__)
Example #2
0
def get_operations(view, operations, autodoc=True):
    if operations is not None:
        return operations

    operations = {}

    # views can be class based
    if view.get("attr"):
        global_meta = load_operations_from_docstring(view["callable"].__doc__)
        if global_meta:
            operations.update(global_meta)
        f_view = getattr(view["callable"], view["attr"])
    # or just function callables
    else:
        f_view = view.get("callable")

    methods = view.get("request_methods")
    view_operations = load_operations_from_docstring(f_view.__doc__)
    if not view_operations:
        view_operations = {}
        if is_string(methods):
            methods = [methods]
        if not methods:
            methods = ALL_METHODS[:]
        operation = load_yaml_from_docstring(f_view.__doc__)
        if operation:
            for method in methods:
                view_operations[method.lower()] = operation
        elif autodoc:
            for method in methods:
                view_operations.setdefault(method.lower(), {"responses": {}})
    operations.update(view_operations)

    return operations
Example #3
0
def _extensions_from_handler(handler_class):
    """Returns extensions dict from handler docstring

    :param handler_class:
    :type handler_class: RequestHandler descendant
    """
    extensions = utils.load_yaml_from_docstring(handler_class.__doc__) or {}
    return extensions
Example #4
0
def _operations_from_methods(handler_class):
    """Generator of operations described in handler's http methods

    :param handler_class:
    :type handler_class: RequestHandler descendant
    """
    for httpmethod in utils.PATH_KEYS:
        method = getattr(handler_class, httpmethod)
        operation_data = utils.load_yaml_from_docstring(method.__doc__)
        if operation_data:
            operation = {httpmethod: operation_data}
            yield operation
def parse_operations(resource):
    """Parse operations for each method in a flask-restful resource"""
    operations = {}
    for method in resource.methods:
        docstring = getattr(resource, method.lower()).__doc__
        if docstring:
            operation = utils.load_yaml_from_docstring(docstring)
            if not operation:
                logging.getLogger(__name__).warning(
                    'Cannot load docstring for {}/{}'.format(resource, method))
            operations[method.lower()] = operation or dict()
    return operations
Example #6
0
def test_load_yaml_from_docstring():
    def f():
        """
        Foo
            bar
            baz quux

        ---
        herp: 1
        derp: 2
        """
    result = utils.load_yaml_from_docstring(f.__doc__)
    assert result == {'herp': 1, 'derp': 2}
Example #7
0
    def update_operations_specs(cls, operations, methods, method=None, **specs):
        operations = operations or {}
        result = {}
        for method_name in methods:
            if method is None and method_name not in cls.methods:
                continue

            method_name = method_name.lower()
            cls_method = method or getattr(cls, method_name, None)
            if not cls_method:
                continue

            defaults = dict(specs)
            defaults.setdefault('consumes', ['application/json'])
            defaults.setdefault('produces', ['application/json'])
            defaults.setdefault('tags', [cls.meta.name])

            docstring = clean_doc(cls_method.__doc__, cls.__doc__)
            if docstring:
                defaults.setdefault('summary', docstring.split('\n')[0])
                defaults.setdefault('description', docstring)

            defaults.setdefault('responses', {
                200: {'description': 'OK', 'content': {'application/json': {}}}
            })
            if cls.Schema:
                defaults['responses'][200]['schema'] = {'$ref': '#/definitions/%s' % cls.meta.name}

            if method_name in ('put', 'patch', 'post'):
                defaults.setdefault('parameters', [])
                schema = {}
                if cls.Schema:
                    schema['$ref'] = '#/definitions/%s' % cls.meta.name

                defaults['parameters'].append({
                    'name': 'body',
                    'in': 'body',
                    'description': 'Resource Body',
                    'required': True,
                    'schema': schema,
                })

            if method_name in operations:
                defaults.update(operations[method_name])

            docstring_yaml = utils.load_yaml_from_docstring(cls_method.__doc__)
            if docstring_yaml:
                defaults.update(docstring_yaml)

            result[method_name] = defaults
        return result
Example #8
0
def operations_from_methods(handler, extensions):
    """Generator of operations described in handler's http methods
    :param handler:
    """
    for httpmethod in utils.PATH_KEYS:
        method = getattr(handler, httpmethod, None)
        if method is None:
            continue
        operation_data = dict()
        operation_data.update(extensions)
        operation_data.update(getattr(method, 'apispec', dict()))
        operation_data.update(
            utils.load_yaml_from_docstring(method.__doc__) or {})
        if operation_data:
            operation = {httpmethod: operation_data}
            yield operation
Example #9
0
    def _add_schema_to_spec(self, schema):
        schema_name = schema.__name__
        data = load_yaml_from_docstring(schema.__doc__) or {}

        missed = set(('name', 'description')) - data.keys()
        if missed:
            for attr in missed:
                data[attr] = schema_name

            missed = ', '.join(f'"{m}"' for m in missed)
            warnings.warn(
                f'For schema <{schema_name}> not provided {missed}.'
                ' Fail to default values.'
                ' Schema class name will be used insted.', RuntimeWarning)

        data['schema'] = schema
        self.spec_schemas.append(data)
Example #10
0
def path_from_view(spec, view, **kwargs):
    """Path helper that allows passing a Flask view function."""
    rule = _rule_for_view(view)
    path = flaskpath2swagger(rule.rule)
    app_root = current_app.config['APPLICATION_ROOT'] or '/'
    path = urljoin(app_root.rstrip('/') + '/', path.lstrip('/'))
    operations = utils.load_operations_from_docstring(view.__doc__)
    path = Path(path=path, operations=operations)
    if hasattr(view, 'view_class') and issubclass(view.view_class, MethodView):
        operations = {}
        for method in view.methods:
            method_name = method.lower()
            method = getattr(view.view_class, method_name)
            docstring_yaml = utils.load_yaml_from_docstring(method.__doc__)
            operations[method_name] = docstring_yaml or dict()
        path.operations.update(operations)
    return path
Example #11
0
 def path_helper(self, operations, view, **kwargs):
     """Path helper that allows passing a Flask view function."""
     rule = self._rule_for_view(view)
     operations.update(
         utils.load_operations_from_docstring(view.__doc__) or {})
     if hasattr(view, 'view_class') and issubclass(view.view_class,
                                                   MethodView):
         for method in view.methods:
             if method in rule.methods:
                 method_name = method.lower()
                 method = getattr(view.view_class, method_name)
                 docstring_yaml = utils.load_yaml_from_docstring(
                     method.__doc__)
                 operations[method_name] = docstring_yaml or dict()
     path = self.flaskpath2openapi(rule.rule)
     app_root = current_app.config['APPLICATION_ROOT'] or '/'
     return urljoin(app_root.rstrip('/') + '/', path.lstrip('/'))
Example #12
0
    def path_helper(self, operations, resource, **kwargs):
        """Path helper that allows passing a Falcon resource instance."""
        resource_uri_mapping = self._generate_resource_uri_mapping(self._app)

        if resource not in resource_uri_mapping:
            raise APISpecError("Could not find endpoint for resource {0}".format(resource))

        operations.update(utils.load_operations_from_docstring(resource.__doc__) or {})
        path = resource_uri_mapping[resource]

        for method in falcon.constants.HTTP_METHODS:
            http_verb = method.lower()
            method_name = "on_" + http_verb
            if hasattr(resource, method_name):
                method = getattr(resource, method_name)
                docstring_yaml = utils.load_yaml_from_docstring(method.__doc__)
                operations[http_verb] = docstring_yaml or dict()

        return path
Example #13
0
    def update_operations_specs(cls,
                                operations,
                                methods,
                                method=None,
                                **specs):
        operations = operations or {}
        result = {}
        for method_name in methods:
            if method is None and method_name not in cls.methods:
                continue

            method_name = method_name.lower()
            cls_method = method or getattr(cls, method_name, None)
            if not cls_method:
                continue

            defaults = dict(specs)
            defaults.setdefault('consumes', ['application/json'])
            defaults.setdefault('produces', ['application/json'])
            defaults.setdefault('tags', [cls.meta.name])

            docstring = clean_doc(cls_method.__doc__, cls.__doc__)
            if docstring:
                defaults.setdefault('summary', docstring.split('\n')[0])
                defaults.setdefault('description', docstring)

            defaults.setdefault('responses', {
                200: {
                    'description': 'OK',
                    'content': {
                        'application/json': {}
                    }
                }
            })
            if cls.Schema:
                defaults['responses'][200]['schema'] = {
                    '$ref': '#/definitions/%s' % cls.meta.name
                }

            if method_name in ('put', 'patch', 'post'):
                defaults.setdefault('parameters', [])
                schema = {}
                if cls.Schema:
                    schema['$ref'] = '#/definitions/%s' % cls.meta.name

                defaults['parameters'].append({
                    'name': 'body',
                    'in': 'body',
                    'description': 'Resource Body',
                    'required': True,
                    'schema': schema,
                })

            if method_name in operations:
                defaults.update(operations[method_name])

            docstring_yaml = utils.load_yaml_from_docstring(cls_method.__doc__)
            if docstring_yaml:
                defaults.update(docstring_yaml)

            result[method_name] = defaults
        return result
Example #14
0
def extensions_from_handler(handler):
    """Returns extensions dict from handler docstring
    :param handler:
    """
    extensions = utils.load_yaml_from_docstring(handler.__doc__) or {}
    return extensions
Example #15
0
def add_pyramid_paths(spec,
                      route_name,
                      request=None,
                      request_method=None,
                      operations=None,
                      **kwargs):
    """

    Adds a route and view info to spec

    :param spec:
        ApiSpec object
    :param route_name:
        Route name to inspect
    :param request:
        Request object, if `None` then `get_current_request()` will be used
    :param request_method:
        Request method predicate
    :param operations:
        Operations dict that will be used instead of introspection
    :param kwargs:
        Additional kwargs for predicate matching
    :return:

    """
    from pyramid.threadlocal import get_current_request

    if request is None:
        request = get_current_request()

    registry = request.registry
    introspector = registry.introspector
    route = introspector.get("routes", route_name)
    views = introspector.related(route)

    # needs to be rewritten to internal name
    if request_method:
        kwargs["request_methods"] = request_method
        # kwargs.setdefault('route_name', route_name)

    for view in views:
        matches = True
        for kw in kwargs.keys():
            # request_methods can be either a list of strings or a string
            # so lets normalize via sets
            if kw == "request_methods":
                if is_string(kwargs[kw]):
                    kwargs[kw] = [kwargs[kw]]
                methods = view.get(kw) or ALL_METHODS
                if is_string(methods):
                    methods = [methods]
                if not set(kwargs[kw] or []).intersection(methods):
                    matches = False
            else:
                if not view.get(kw) == kwargs[kw]:
                    matches = False

        if not matches:
            continue

        final_operations = {}

        # views can be class based
        if view.get("attr"):
            global_meta = load_operations_from_docstring(
                view["callable"].__doc__)
            if global_meta:
                final_operations.update(global_meta)
            f_view = getattr(view["callable"], view["attr"])
        # or just function callables
        else:
            f_view = view.get("callable")

        if operations is None:
            methods = view.get("request_methods")
            view_operations = load_operations_from_docstring(f_view.__doc__)
            if not view_operations:
                view_operations = {}
                if is_string(methods):
                    methods = [methods]
                if not methods:
                    methods = ALL_METHODS[:]
                operation = load_yaml_from_docstring(f_view.__doc__)
                if operation:
                    for method in methods:
                        view_operations[method.lower()] = operation
            final_operations.update(view_operations)
        else:
            final_operations = operations
        spec.add_path(route["pattern"], operations=final_operations)
Example #16
0
def operations_merging(spec, path, operations, view, **kwargs):
    """operations helper that allows passing an annotated function."""

    dict_of_missing = temp_dict_missing(DictDefaultWrap())

    hierarchy_docs = []
    hierarchy_func = view
    while hierarchy_func:
        doc = getattr(hierarchy_func, '__orig_doc__', None)
        if doc:
            doc_yaml = utils.load_yaml_from_docstring(doc)
            if doc_yaml:
                hierarchy_docs.append(doc_yaml)
        hierarchy_func = getattr(hierarchy_func, '__wrapped__', None)

    def parameters_merging(o_parameters, d_parameters, yaml_doc):
        o_parameter_ids = {}
        for parameter in o_parameters:
            try:
                o_parameter_ids[(parameter['in'],
                                 parameter['name'])] = parameter
            except KeyError as e:
                raise IncompleteParameterSetting(
                    "While solving parameter {}, with yaml document {}, related to view with doc {},"
                    "the parameter attribute '{}' was missing".format(
                        parameter, yaml_doc, view, e.args[0]))

        for parameter in d_parameters['parameters']:
            try:
                param_in = parameter['in']
                param_name = parameter['name']
            except KeyError as e:
                raise IncompleteParameterSetting(
                    "While solving parameter {}, with yaml document {}, related to view with doc {},"
                    "the parameter attribute '{}' was missing".format(
                        parameter, yaml_doc, view, e.args[0]))

            o_parameter = o_parameter_ids.pop((param_in, param_name), None)
            if not o_parameter:
                o_parameter = {}
                o_parameters.append(o_parameter)

    def merge_with_default(original, defaults, yaml_doc=None):
        for operation_name, d_operation_content in defaults.items():
            o_operation_content = original.setdefault(operation_name,
                                                      d_operation_content)
            if o_operation_content is d_operation_content:
                if isinstance(o_operation_content, str):
                    # This allows making placeholders for the string that were never set and not raising as a result.
                    with dict_of_missing(original) as defaulted_dict:
                        original[operation_name] = o_operation_content.format(
                            f=defaulted_dict)
                continue
            if isinstance(o_operation_content, dict):
                merge_with_default(o_operation_content, d_operation_content,
                                   yaml_doc)
            elif isinstance(o_operation_content, list):
                if operation_name == "parameters":
                    parameters_merging(o_operation_content,
                                       d_operation_content, yaml_doc)
                elif operation_name == 'security':
                    o_security_methods = {
                        tuple(security_rule.keys())
                        for security_rule in o_operation_content
                    }
                    new_auth_methods = [
                        security_rule for security_rule in d_operation_content
                        if tuple(security_rule.keys()) not in
                        o_security_methods
                    ]
                    o_operation_content.extend(new_auth_methods)

                    pass
                elif operation_name in ('produces', 'consumes', 'tags'):
                    o_operation_content[:] = list(
                        OrderedDict.fromkeys(
                            itertools.chain(o_operation_content,
                                            d_operation_content)).keys())

    fallover_defaults = []
    for hierarchy_doc in hierarchy_docs:
        try:
            fallover_defaults.append(hierarchy_doc.pop('_'))
        except KeyError:
            pass
        merge_with_default(path.operations, hierarchy_doc, view.__doc__)
    if fallover_defaults:
        underscore_default = fallover_defaults.pop(0)
        # Algorithmically, it is faster O(_n) if I merge all operations for all methods first than if I
        # run the same defaults sequence for all methods
        for default in fallover_defaults:
            merge_with_default(underscore_default, default, view.__doc__)
        for method, method_operations in path.operations.items():
            merge_with_default(method_operations, underscore_default,
                               view.__doc__)

    return path