Ejemplo n.º 1
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
Ejemplo n.º 2
0
def get_operations(spec,
                   uri_pattern,
                   view,
                   operations,
                   show_head,
                   show_options,
                   cornice_service,
                   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[:]
        if 'HEAD' in methods and not show_head:
            methods.remove('HEAD')
        if 'OPTIONS' in methods and not show_options:
            methods.remove('OPTIONS')
        operation = load_yaml_from_docstring(f_view.__doc__)
        if operation:
            for method in methods:
                view_operations[method.lower()] = operation
        elif autodoc:
            path_parameters = get_uri_placeholders(uri_pattern)
            for method in methods:
                auto_doc = AutoDoc(method, view, cornice_service)
                if path_parameters:
                    auto_doc.add_path_parameter(path_parameters)
                request_schema = auto_doc.find_schema_for('body')
                if request_schema:
                    add_schema_in_spec(spec, request_schema)
                view_operations = auto_doc.to_dict()

    operations.update(view_operations)

    return operations
Ejemplo n.º 3
0
 def path_helper(self, operations, *, view, **kwargs):
     """Path helper that allows passing a bottle view function."""
     operations.update(
         yaml_utils.load_operations_from_docstring(view.__doc__))
     app = kwargs.get("app", _default_app)
     route = self._route_for_view(app, view)
     return self.bottle_path_to_openapi(route.rule)
Ejemplo n.º 4
0
    def _apispec(self):

        info = {}
        if self.description is not None:
            info["description"] = self.description
        if self.terms_of_service is not None:
            info["termsOfService"] = self.terms_of_service
        if self.contact is not None:
            info["contact"] = self.contact
        if self.license is not None:
            info["license"] = self.license

        spec = APISpec(
            title=self.title,
            version=self.version,
            openapi_version=self.openapi_version,
            plugins=[MarshmallowPlugin()],
            info=info,
        )

        for route in self.routes:
            if self.routes[route].description:
                operations = yaml_utils.load_operations_from_docstring(
                    self.routes[route].description)
                spec.path(path=route, operations=operations)

        for name, schema in self.schemas.items():
            spec.components.schema(name, schema=schema)

        return spec
Ejemplo n.º 5
0
    def path_helper(self, operations, resource, base_path=None, **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(
            yaml_utils.load_operations_from_docstring(resource.__doc__) or {})
        path = resource_uri_mapping[resource]["uri"]

        if base_path is not None:
            # make sure base_path accept either with or without leading slash
            # swagger 2 usually come with leading slash but not in openapi 3.x.x
            base_path = '/' + base_path.strip('/')
            path = re.sub(base_path, "", path, 1)

        methods = resource_uri_mapping[resource]["methods"]

        for method_name, method_handler in methods.items():
            docstring_yaml = yaml_utils.load_yaml_from_docstring(
                method_handler.__doc__)
            operations[method_name] = docstring_yaml or dict()
        return path
Ejemplo n.º 6
0
    def path_helper(self, resource, operations, path=None, **kwargs) -> str:
        operations.update(yaml_utils.load_operations_from_docstring(resource.__doc__) or {})
        path = path or self.resource_uri_mapping[resource]["uri"]

        methods = self.resource_uri_mapping[resource]["methods"]

        for method_name, method_handler in methods.items():
            docstring_yaml = yaml_utils.load_yaml_from_docstring(method_handler.__doc__)
            operations[method_name] = docstring_yaml or {}
        return path
Ejemplo n.º 7
0
 def path_helper(self, operations, *, view, app, **kwargs):
     """Path helper that allows passing a chalice view function."""
     if not isinstance(app, Chalice):
         raise APISpecError(f"app must be an instance of Chalice")
     # parse through the documentation string to see what operations are defined
     operations.update(
         yaml_utils.load_operations_from_docstring(view.__doc__))
     # find the route for this view function
     route = self._route_for_view(app, operations, view)
     # if we didn't throw an exception then the view function handles all operations found in the docs
     return route
Ejemplo n.º 8
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(yaml_utils.load_operations_from_docstring(view.__doc__))
     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)
                 operations[method_name] = yaml_utils.load_yaml_from_docstring(method.__doc__)
     return self.flaskpath2openapi(rule.rule)
Ejemplo n.º 9
0
    def wrapper(func):
        # get the file
        path = Path(spec_path)
        if not path.exists():
            return func

        # get the content
        content = path.read_text()

        # save the content in a special attribute of the function
        func.__apispec__ = func.__dict__.get("__apispec__", {})
        func.__apispec__.update(load_operations_from_docstring(content))

        return func
Ejemplo n.º 10
0
    def _apispec(self):
        spec = APISpec(title=self.title,
                       version=self.version,
                       openapi_version=self.openapi_version,
                       plugins=[MarshmallowPlugin()],
                       **self._apispec_options)

        for route in self.routes:
            if self.routes[route].description:
                operations = yaml_utils.load_operations_from_docstring(
                    self.routes[route].description)
                spec.path(path=route, operations=operations)

        for name, schema in self.schemas.items():
            spec.components.schema(name, schema=schema)

        return spec
Ejemplo n.º 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(
         yaml_utils.load_operations_from_docstring(view.__doc__))
     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)
                 operations[
                     method_name] = yaml_utils.load_yaml_from_docstring(
                         method.__doc__)
     path = self.flaskpath2openapi(rule.rule)
     app_root = current_app.config['APPLICATION_ROOT'] or '/'
     return urljoin(app_root.rstrip('/') + '/', path.lstrip('/'))
Ejemplo n.º 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(yaml_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 getattr(resource, method_name, None) is not None:
                method = getattr(resource, method_name)
                docstring_yaml = yaml_utils.load_yaml_from_docstring(method.__doc__)
                operations[http_verb] = docstring_yaml or dict()

        return path
Ejemplo n.º 13
0
def initialize_routes(api):
    api.register_blueprint(comics, url_prefix='/comics')
    api.register_blueprint(docs)

    with api.test_request_context():
        for r in api.url_map.iter_rules():
            view = api.view_functions.get(r.endpoint)
            operations = load_operations_from_docstring(view.__doc__)
            path = FlaskPlugin.flaskpath2openapi(r.rule)
            if not operations:
                continue
            # De-reference the schemas referenced in the docstring.
            for verb in operations:
                resp = operations.get(verb).get('responses')
                for status in resp:
                    val = resp.get(status)
                    content = resp.get(status).get('schema')
                    if content:
                        pass
            # Add paths to the spec
            spec.path(view=view, path=path, operations=operations)
Ejemplo n.º 14
0
    def path_helper(self,
                    path=None,
                    operations: dict = None,
                    parameters: list = None,
                    **kwargs):
        """Path helper that allows passing a Falcon resource instance."""
        uri_to_method_map = self._get_uri_falcon_details_mapping()

        if path not in uri_to_method_map:
            raise APISpecError(f"Could not find handlers for path='{path}'")
        falcon_routing_details = uri_to_method_map[path]
        resource = falcon_routing_details["resource"]
        operations.update(
            yaml_utils.load_operations_from_docstring(resource.__doc__) or {})

        methods = falcon_routing_details["methods"]

        for method_name, method_handler in methods.items():
            docstring_yaml = yaml_utils.load_yaml_from_docstring(
                method_handler.__doc__)
            operations[method_name] = docstring_yaml or dict()
        return path
Ejemplo n.º 15
0
    def update_specs(cls, specs):
        schema_name = cls.meta.name
        if cls.Schema:
            schema_name = cls.Schema.__name__.replace('Schema', '')
            if schema_name not in specs.components._schemas:
                specs.components.schema(schema_name, schema=cls.Schema)

        operations = yaml_utils.load_operations_from_docstring(cls.__doc__)

        openapi_url = convert_flask_url_to_openapi(cls.meta.url)
        path_parameters = cls.build_path_parameters(openapi_url)
        operations_spec = cls.update_operations_specs(
            operations, ('GET', 'POST'),
            detail=None if not cls.meta.url_detail else False,
            parameters=path_parameters)
        specs.path(openapi_url, operations=operations_spec)

        if cls.meta.url_detail:
            openapi_url = convert_flask_url_to_openapi(cls.meta.url_detail)
            path_parameters = cls.build_path_parameters(openapi_url)
            operations_spec = cls.update_operations_specs(
                operations, ('GET', 'PUT', 'PATCH', 'DELETE'),
                detail=True,
                parameters=path_parameters)
            specs.path(openapi_url, operations=operations_spec)

        for endpoint, (url_, name_, params_) in cls.meta.endpoints.values():
            openapi_url = convert_flask_url_to_openapi(
                f"{cls.meta.url.rstrip('/')}/{url_}")
            path_parameters = cls.build_path_parameters(openapi_url)
            operations_spec = cls.update_operations_specs(
                operations,
                params_.get('methods', ('GET', )),
                method=getattr(cls, name_, None),
                detail=None,
                parameters=path_parameters)
            specs.path(openapi_url, operations=operations_spec)
Ejemplo n.º 16
0
def test_from_file_decorator(tmp_path):
    """ Test the from_file decorator. """
    # create a temp yaml file
    yaml_content = """
    ---
    get:
      summary: Hello
      operationId: hello
      responses:
        200:
          content:
            application/json:
              schema:
                type: string
    """
    yaml_file = tmp_path / "hello.yml"
    yaml_file.write_text(yaml_content)

    # decorate a function with the from_file decorator
    @from_file(str(yaml_file))
    def hello():
        return {"hello"}

    assert load_operations_from_docstring(yaml_content) == hello.__apispec__
Ejemplo n.º 17
0
    def path_helper(self, operations, resource, base_path=None, **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(yaml_utils.load_operations_from_docstring(resource.__doc__) or {})
        path = resource_uri_mapping[resource]

        if base_path is not None:
            # make sure base_path accept either with or without leading slash
            # swagger 2 usually come with leading slash but not in openapi 3.x.x
            base_path = '/' + base_path.strip('/')
            path = re.sub(base_path, "", path, 1)

        for method in falcon.constants.HTTP_METHODS:
            http_verb = method.lower()
            method_name = "on_" + http_verb
            if getattr(resource, method_name, None) is not None:
                method = getattr(resource, method_name)
                docstring_yaml = yaml_utils.load_yaml_from_docstring(method.__doc__)
                operations[http_verb] = docstring_yaml or dict()
        return path
Ejemplo n.º 18
0
def test_load_operations_from_docstring_empty_docstring(docstring):
    assert yaml_utils.load_operations_from_docstring(docstring) == {}
Ejemplo n.º 19
0
async def get_spec(socket: Socket, request_id: str) -> APISpec:
    """
    Construct open api spec by interrogating FlowMachine.

    Parameters
    ----------
    socket : Socket
    request_id : str
        Unique id of the request

    Returns
    -------
    APISpec
        The specification object

    """
    msg = {"request_id": request_id, "action": "get_query_schemas"}
    socket.send_json(msg)
    #  Get the reply.
    reply = await socket.recv_json()
    flowmachine_query_schemas = reply["payload"]["query_schemas"]
    # Need to mark query_kind as a required field
    # this is a workaround because the marshmallow-oneOf plugin strips
    # the query_kind off, which means it can't be required from the marshmallow
    # side without raising an error
    for schema, schema_dict in flowmachine_query_schemas.items():
        try:
            if "query_kind" in schema_dict["properties"]:
                schema_dict["required"].append("query_kind")
        except KeyError:
            pass  # Doesn't have any properties
    spec = APISpec(
        title="FlowAPI",
        version=__version__,
        openapi_version="3.0.1",
        info=dict(
            description="FlowKit Analytical API",
            license=dict(name="MPLv2",
                         url="https://www.mozilla.org/en-US/MPL/2.0/"),
            contact=dict(email="*****@*****.**"),
        ),
    )
    spec.components.schemas.update(flowmachine_query_schemas)
    spec.components.security_scheme(
        "token",
        {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "JWT",
            "x-security-scopes": sorted(schema_to_scopes(spec.to_dict())),
            "x-audience": current_app.config["JWT_DECODE_AUDIENCE"],
        },
    )
    # Loop over all the registered views and try to parse a yaml
    # openapi spec from their docstrings
    for rule in current_app.url_map.iter_rules():
        try:
            func = current_app.view_functions[rule.endpoint]
            operations = yaml_utils.load_operations_from_docstring(
                func.__doc__)
            if len(operations) > 0:
                for method, op in operations.items():
                    op["operationId"] = f"{rule.endpoint}.{method}"
                spec.path(
                    path=rule.rule,
                    operations=operations,
                )
        except Exception as e:
            pass  # Don't include in API

    return spec
Ejemplo n.º 20
0
async def get_spec(socket: Socket, request_id: str) -> APISpec:
    """
    Construct open api spec by interrogating FlowMachine.

    Parameters
    ----------
    socket : Socket
    request_id : str
        Unique id of the request

    Returns
    -------
    APISpec
        The specification object

    """
    msg = {"request_id": request_id, "action": "get_query_schemas"}
    socket.send_json(msg)
    #  Get the reply.
    reply = await socket.recv_json()
    flowmachine_query_schemas = reply["payload"]["query_schemas"]
    # Need to mark query_kind as a required field
    # this is a workaround because the marshmallow-oneOf plugin strips
    # the query_kind off, which means it can't be required from the marshmallow
    # side without raising an error
    for schema, schema_dict in flowmachine_query_schemas.items():
        try:
            schema_dict["required"].append("query_kind")
        except KeyError:
            pass  # Doesn't have any properties
    spec = APISpec(
        title="FlowAPI",
        version=__version__,
        openapi_version="3.0.1",
        info=dict(
            description="FlowKit Analytical API",
            license=dict(name="MPLv2", url="https://www.mozilla.org/en-US/MPL/2.0/"),
            contact=dict(email="*****@*****.**"),
        ),
    )
    spec.components._schemas = flowmachine_query_schemas
    spec.components.security_scheme(
        "token", dict(type="http", scheme="bearer", bearerFormat="JWT")
    )
    # Loop over all the registered views and try to parse a yaml
    # openapi spec from their docstrings
    for endpoint_func_name, rule in current_app.url_map.endpoints.items():
        try:
            func = current_app.view_functions[endpoint_func_name]
            operations = yaml_utils.load_operations_from_docstring(func.__doc__)
            if len(operations) > 0:
                for method, op in operations.items():
                    op["operationId"] = f"{endpoint_func_name}.{method}"
                spec.path(
                    path=rule[
                        0
                    ].rule,  # In theory, could have multiple rules that match but will only be a single one here
                    operations=operations,
                )
        except Exception as e:
            pass  # Don't include in API

    return spec
Ejemplo n.º 21
0
    def operation_helper(self, path, operations, router, func, **kwargs):
        new_operations = load_operations_from_docstring(func.__doc__)

        if hasattr(func, '_cp_config') is False:
            return None

        cp_config = func._cp_config

        if new_operations is not None:
            for method, data in new_operations.items():

                if cp_config.get('tools.authentication.on', True):
                    data['security'] = [{'Bearer': []}]

                if 'tools.model_in.cls' in cp_config:
                    model_cls = cp_config['tools.model_in.cls']
                    try:
                        self.spec.components.schema(model_cls.__name__,
                                                    component=parse_model(
                                                        self.spec, model_cls))
                    except DuplicateComponentNameError:
                        pass

                    data['requestBody']['required'] = True
                    data['requestBody']['content'] = {
                        'application/json': {
                            'schema': {
                                '$ref':
                                '#/components/schemas/' + model_cls.__name__
                            }
                        }
                    }

                if 'tools.model_params.cls' in cp_config:
                    model_cls = cp_config['tools.model_params.cls']
                    data['parameters'] = data.get('parameters', [])

                    # In query vs in path
                    for key, obj in model_cls.__dict__.items():
                        inn = 'query'
                        if '{' + key + '}' in path:
                            inn = 'path'
                        if isinstance(obj, FieldDescriptor):
                            paramenters = {
                                'name':
                                key,
                                'in':
                                inn,
                                'required':
                                model_cls._fields[key].required,
                                'schema':
                                parse_model_type(self.spec,
                                                 model_cls._fields[key]),
                            }
                            if isinstance(model_cls._fields[key]._default,
                                          UndefinedType) is False:
                                paramenters['schema'][
                                    'default'] = model_cls._fields[
                                        key]._default
                            data['parameters'].append(paramenters)

                if 'tools.model_out.cls' in cp_config:
                    model_cls = cp_config['tools.model_out.cls']
                    try:
                        self.spec.components.schema(model_cls.__name__,
                                                    component=parse_model(
                                                        self.spec, model_cls))
                    except DuplicateComponentNameError:
                        pass
                    data['responses'][200]['content'] = {
                        'application/json': {
                            'schema': {
                                '$ref':
                                '#/components/schemas/' + model_cls.__name__
                            }
                        }
                    }

                if 'tools.model_out_pagination.cls' in cp_config:
                    model_cls = cp_config['tools.model_out_pagination.cls']
                    try:
                        self.spec.components.schema(model_cls.__name__,
                                                    component=parse_model(
                                                        self.spec, model_cls))
                    except DuplicateComponentNameError:
                        pass
                    self.spec.components.schema(
                        "List" + model_cls.__name__,
                        component={
                            'type': 'object',
                            'properties': {
                                path.split("/")[-1]: {
                                    'type': 'array',
                                    'items': {
                                        '$ref':
                                        '#/components/schemas/' +
                                        model_cls.__name__
                                    }
                                },
                                path.split("/")[-1] + "_links": {
                                    'type': 'array',
                                    'items': {
                                        'type': 'object',
                                        'properties': {
                                            'href': {
                                                'type': 'string'
                                            },
                                            'rel': {
                                                'type': 'string'
                                            }
                                        }
                                    }
                                }
                            },
                        })
                    data['responses'][200]['content'] = {
                        'application/json': {
                            'schema': {
                                '$ref':
                                '#/components/schemas/' + "List" +
                                model_cls.__name__
                            }
                        }
                    }

                if 'tools.enforce_permission.permission_name' in cp_config:
                    data['x-required-permission'] = cp_config[
                        'tools.enforce_permission.permission_name']

        operations.update(new_operations)
        return None
Ejemplo n.º 22
0
 def path_helper(self, operations, *, view, app=None, **kwargs):
     """Path helper that allows passing a Flask view function."""
     rule = self._rule_for_view(view)
     operations.update(
         yaml_utils.load_operations_from_docstring(view.__doc__))
     return self.flaskpath2openapi(rule.rule)
Ejemplo n.º 23
0
async def generate_openapi_spec(app: web.Application) -> None:
    accepted_methods = {METH_GET, METH_PUT, METH_POST, METH_DELETE, METH_PATCH}
    paths = {}
    schemas = []
    options = {
        "info": {
            "description":
            "[Cookie Authentication](https://swagger.io/docs/specification/authentication/cookie-authentication/) "
            "[OAuth2 Implicit Grant and SPA](https://auth0.com/blog/oauth2-implicit-grant-and-spa/)"
        },
        "securitySchemes": {
            "cookieAuth": {
                "type": "apiKey",
                "in": "cookie",
                "name": app["settings"]["session"]["cookie"]["cookie_name"]
            }
        }
    }

    spec = APISpec(title=app["settings"]["name"],
                   version=app["settings"]["version"],
                   openapi_version="3.0.2",
                   plugins=[MarshmallowPlugin()],
                   **options)

    routes = [
        route for route in app.router.routes()
        if route.name != app["settings"]["openapi"]["route"]["name"]
    ]

    for route in routes:
        operations: Dict = {}

        if issubclass_py37(route.handler,
                           web.View) and route.method == METH_ANY:
            for attr in dir(route.handler):
                attr_uc = attr.upper()

                if attr_uc in METH_ALL and attr_uc in accepted_methods:
                    docstring_source = getattr(route.handler, attr)
                    operations = load_operations_from_docstring(
                        docstring_source.__doc__)
        else:
            docstring_source = route.handler
            operations = load_operations_from_docstring(
                docstring_source.__doc__)

        if operations:
            info = route.get_info()
            path = info.get('path') or info.get('formatter')

            for schema in lookup("schema", operations):
                if schema not in schemas:
                    schemas.append(schema)

            if path not in paths.keys():
                paths[path] = {}

            paths[path].update(operations)

    for schema in schemas:
        schema_class = getattr(schemas_module, schema)
        if callable(schema_class):
            schema_name = re.match(r"([^/]+?)(?:Schema)*$", schema).group(1)
            spec.components.schema(schema_name, schema=schema_class)

    for path, operations in paths.items():
        spec.path(path=path, operations=operations)

    app["openapi"] = spec
Ejemplo n.º 24
0
 def operation_helper(self, operations, func, **kwargs):
     """Operation helper that parses docstrings for operations. Adds a ``func`` parameter to `apispec.APISpec.path`.
     """
     doc_operations = load_operations_from_docstring(func.__doc__)
     operations.update(doc_operations)