Exemplo n.º 1
0
def generate_api_docs(filename, api_version, openapi_version):
    """
        Generates swagger (openapi) yaml from routes' docstrings (using apispec library).
    """
    from apispec import APISpec
    from apispec_webframeworks.flask import FlaskPlugin

    apidoc = APISpec(
        title="Grafolean API",
        version=api_version,
        openapi_version=openapi_version,
        plugins=[FlaskPlugin()],
        info={
            "description":
                "> IMPORTANT: API is under development and is **not final yet** - breaking changes might occur.\n\n" \
                "Grafolean is designed API-first. In other words, every functionality of the system is accessible through the API " \
                "described below. This allows integration with external systems so that (given the permissions) they too can " \
                "enter values, automatically modify entities, set up dashboards... Everything that can be done through frontend " \
                "can also be achieved through API.\n\n" \
                "This documentation is also available as Swagger/OpenAPI definition: [OAS2](./swagger2.yml) / [OAS3](./swagger3.yml)."
        }
    )

    for apidoc_schemas_func in [users_apidoc_schemas, accounts_apidoc_schemas, admin_apidoc_schemas]:
        for schema_name, schema in apidoc_schemas_func():
            apidoc.components.schema(schema_name, schema)

    with app.test_request_context():
        for rule in app.url_map.iter_rules():
            view = app.view_functions.get(rule.endpoint)
            apidoc.path(view=view)

    with open(filename, 'w') as openapi_yaml_file:
        openapi_yaml_file.write(apidoc.to_yaml())
Exemplo n.º 2
0
def generate_swagger() -> str:

    # Create an APISpec
    spec = APISpec(
        title="ARCOR2 Data Models",
        version=arcor2.api_version(),
        openapi_version="3.0.2",
        plugins=[FlaskPlugin(), DataclassesPlugin()],
    )

    # TODO avoid explicit naming of all sub-modules in rpc module
    for module in (arcor2.data.common, arcor2.data.object_type, rpc.common,
                   rpc.execution, rpc.objects, rpc.robot, rpc.scene,
                   rpc.project, rpc.services, rpc.storage, arcor2.data.events):
        for name, obj in inspect.getmembers(module):

            if not inspect.isclass(obj) or not issubclass(
                    obj, JsonSchemaMixin) or obj == JsonSchemaMixin:
                continue

            try:
                spec.components.schema(obj.__name__, schema=obj)
            except DuplicateComponentNameError:
                continue

    return spec.to_yaml()
Exemplo n.º 3
0
class Spec:
    def __init__(self, api, title, version):
        self.obj = API_Spec(title=title, version=version, openapi_version='2.0',
                            produces=['application/json'], consumes=['application/json'],
                            tags=rc_tags,
                            plugins=[Falcon_Plugin(api), Marshmallow_Plugin(schema_name_resolver=self.__schema_name_resolver)])

    def get(self):
        return self.obj

    def write(self):
        path = Path(__file__).parent / '../swagger/schema.yaml'
        with path.open('w') as file:
            file.write(self.obj.to_yaml())
        path = Path(__file__).parent / '../swagger/schema.json'
        with path.open('w') as file:
            file.write(dumps(self.obj.to_dict(), indent=2))

    @staticmethod
    def __schema_name_resolver(schema):
        if is_str(schema):
            ref = schema
        else:
            ref = schema.__class__.__name__
        return ref.replace('_Schema', '')
Exemplo n.º 4
0
def write_yaml_file(spec: APISpec):
    """ Экспортируем объект APISpec в YAML файл.

    :param spec: объект APISpec
    """
    with open(DOCS_FILENAME, 'w') as file:
        file.write(spec.to_yaml())
    print(f'Сохранили документацию в {DOCS_FILENAME}')
Exemplo n.º 5
0
def run_app(
    app: Flask,
    name: str,
    version: str,
    api_version: str,
    port: int,
    dataclasses: Optional[List[Type[JsonSchemaMixin]]] = None,
    print_spec: bool = False,
) -> None:

    spec = APISpec(
        title=f"{name} ({version})",
        version=api_version,
        openapi_version="3.0.2",
        plugins=[FlaskPlugin(), DataclassesPlugin()],
    )

    if dataclasses is not None:
        for dc in dataclasses:
            spec.components.schema(dc.__name__, schema=dc)

    with app.test_request_context():
        for rule in app.url_map.iter_rules():
            if rule.endpoint != "static":
                spec.path(view=app.view_functions[rule.endpoint])

    if print_spec:
        print(spec.to_yaml())
        return

    @app.route("/swagger/api/swagger.json", methods=["GET"])
    def get_swagger() -> str:
        return jsonify(spec.to_dict())

    @app.errorhandler(Arcor2Exception)
    def handle_bad_request_general(e: Arcor2Exception) -> Tuple[str, int]:
        return json.dumps(str(e)), 400

    @app.errorhandler(FlaskException)
    def handle_bad_request_intentional(e: FlaskException) -> Tuple[str, int]:
        return json.dumps(str(e)), e.error_code

    SWAGGER_URL = "/swagger"

    swaggerui_blueprint = get_swaggerui_blueprint(
        SWAGGER_URL,
        "./api/swagger.json"  # Swagger UI static files will be mapped to '{SWAGGER_URL}/dist/'
    )

    # Register blueprint at URL
    app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)

    if not env.get_bool("ARCOR2_REST_API_DEBUG", False):
        # turn off logging each endpoint call by default
        log = logging.getLogger("werkzeug")
        log.setLevel(logging.ERROR)

    app.run(host="0.0.0.0", port=port)
Exemplo n.º 6
0
    def convert(self, yaml=False, references=True, ignorespec=None, **options):
        spec = APISpec(title=self.info.get('title'),
                       version=self.info.get('version'),
                       openapi_version=self.openapi,
                       plugins=[MultiOperationBuilderPlugin(references)],
                       **options)

        for requestitem in self.postman_collection.get_requestitems():
            spec.path(path=requestitem.get_request().path,
                      operations=Operation(requestitem,
                                           ignorespec=ignorespec).get())
        return spec.to_yaml() if yaml else spec.to_dict()
Exemplo n.º 7
0
def openapi_format(format="yaml", server="localhost", no_servers=False):
    extra_specs = {
        'info': {
            'description':
            'The Faraday REST API enables you to interact with '
            '[our server](https://github.com/infobyte/faraday).\n'
            'Use this API to interact or integrate with Faraday'
            ' server. This page documents the REST API, with HTTP'
            ' response codes and example requests and responses.'
        },
        'security': {
            "ApiKeyAuth": []
        }
    }

    if not no_servers:
        extra_specs['servers'] = [{'url': f'https://{server}/_api'}]

    spec = APISpec(
        title="Faraday API",
        version="2",
        openapi_version="3.0.2",
        plugins=[FaradayAPIPlugin(),
                 FlaskPlugin(),
                 MarshmallowPlugin()],
        **extra_specs)
    api_key_scheme = {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization"
    }

    spec.components.security_scheme("API_KEY", api_key_scheme)
    response_401_unauthorized = {
        "description":
        "You are not authenticated or your API key is missing "
        "or invalid"
    }
    spec.components.response("UnauthorizedError", response_401_unauthorized)

    with app.test_request_context():
        for endpoint in app.view_functions:
            spec.path(view=app.view_functions[endpoint], app=app)
        if format.lower() == "yaml":
            print(spec.to_yaml())
        else:
            print(json.dumps(spec.to_dict(), indent=2))
Exemplo n.º 8
0
def generate_swagger_file():
    """Automatically generates Swagger spec file based on RequestHandler
    docstrings and saves it to the specified file_location.
    """
    errors = []

    file_location = os.path.join(os.path.dirname(__file__), '..', '..', 'docs',
                                 'api_schema_v{}'.format(API_VERSION))

    # Starting to generate Swagger spec file. All the relevant
    # information can be found from here https://apispec.readthedocs.io/
    security_settings = yaml.safe_load(OPENAPI_SPEC_SECURITY)
    spec = APISpec(
        title="Unmanic API",
        version=str(API_VERSION),
        openapi_version="3.0.0",
        info=dict(description="Documentation for the Unmanic application API"),
        plugins=[UnmanicSpecPlugin(), MarshmallowPlugin()],
        servers=[
            {
                "url":
                "http://localhost:8888/unmanic/api/v{}/".format(API_VERSION),
                "description": "Local environment",
            },
        ],
        **security_settings)
    # Looping through all the handlers and trying to register them.
    # Handlers without docstring will raise errors. That's why we
    # are catching them silently.
    handlers = find_all_handlers()
    for handler in handlers:
        try:
            spec.path(urlspec=handler)
        except APISpecError as e:
            errors.append("API Docs - Failed to append spec path - {}".format(
                str(e)))
            pass

    # Write the Swagger file into specified location.
    with open('{}.json'.format(file_location), "w", encoding="utf-8") as file:
        json.dump(spec.to_dict(), file, ensure_ascii=False, indent=4)
    # TODO: Remove YAML. It sucks!
    with open('{}.yaml'.format(file_location), "w", encoding="utf-8") as file:
        file.write(spec.to_yaml())

    return errors
Exemplo n.º 9
0
def openapi_format(format="yaml"):

    spec = APISpec(
        title="Faraday API",
        version="2",
        openapi_version="3.0.2",
        plugins=[FaradayAPIPlugin(), MarshmallowPlugin()],
        info={'description': 'The Faraday server API'},
    )

    with app.test_request_context():
        for endpoint in app.view_functions:
            spec.path(view=app.view_functions[endpoint], app=app)
        if format.lower() == "yaml":
            print(spec.to_yaml())
        else:
            print(json.dumps(spec.to_dict(), indent=2))
Exemplo n.º 10
0
def init_docs(app):
    ctx = app.test_request_context()
    ctx.push()
    settings = yaml.safe_load(OPENAPI_SPEC)

    # Create an APISpec
    spec = APISpec(
        title="Swagger Project",
        version="1.0.0",
        openapi_version="3.0.2",
        plugins=[FlaskPlugin()],
        **settings
    )
    setup_schema_definition(spec)
    setup_path(spec)
    with open("project/docs/swagger.yml", "w") as swagger_file:
        swagger_file.write(spec.to_yaml())
    app.config["SWAGGER"] = {"title": "Swagger Project", "openapi": "3.0.2"}
    Swagger(app, template=spec.to_dict())
Exemplo n.º 11
0
def generate_openapi(service_name: str, version: str, rpcs: List[Type[RPC]],
                     events: List[Type[Event]]) -> str:
    """Generate OpenAPI models from RPCs and events.

    Be aware: it modifies __name__ attribute!
    """

    # Create an APISpec
    spec = APISpec(
        title=f"{service_name} Data Models",
        version=version,
        openapi_version="3.0.2",
        plugins=[FlaskPlugin(), DataclassesPlugin()],
    )

    for obj in events:

        if obj is Event:
            continue

        _rename_childs(obj)

    for rpc in rpcs:

        if rpc is RPC:
            continue

        for cls in (rpc.Request, rpc.Response):

            cls.__name__ = rpc.__name__ + cls.__name__
            _rename_childs(cls)

    for obj in events:
        _add_to_spec(spec, obj)
    for rpc in rpcs:
        for cls in (rpc.Request, rpc.Response):
            _add_to_spec(spec, cls)

    return spec.to_yaml()
Exemplo n.º 12
0
class OpenAPISpec:
    def __init__(self, *args, **kwargs):
        self.spec = APISpec(*args, **kwargs)

    @property
    def to_yaml(self):
        return self.spec.to_yaml()

    @property
    def to_dict(self):
        return self.spec.to_dict()

    @property
    def components(self):
        return self.spec.components

    def path(self, path):
        def wrapped(func):
            self.spec.path(path,
                           description=func.__doc__.partition('\n')[0],
                           operations=load_yaml_from_docstring(func.__doc__))
            return func

        return wrapped
Exemplo n.º 13
0
def openapi_format(format="yaml",
                   server="localhost",
                   no_servers=False,
                   return_tags=False):
    extra_specs = {
        'info': {
            'description':
            'The Faraday REST API enables you to interact with '
            '[our server](https://github.com/infobyte/faraday).\n'
            'Use this API to interact or integrate with Faraday'
            ' server. This page documents the REST API, with HTTP'
            ' response codes and example requests and responses.'
        },
        'security': {
            "ApiKeyAuth": []
        }
    }

    if not no_servers:
        extra_specs['servers'] = [{'url': f'https://{server}/_api'}]

    spec = APISpec(
        title="Faraday API",
        version="2",
        openapi_version="3.0.2",
        plugins=[FaradayAPIPlugin(),
                 FlaskPlugin(),
                 MarshmallowPlugin()],
        **extra_specs)
    api_key_scheme = {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization"
    }

    spec.components.security_scheme("API_KEY", api_key_scheme)
    response_401_unauthorized = {
        "description":
        "You are not authenticated or your API key is missing "
        "or invalid"
    }
    spec.components.response("UnauthorizedError", response_401_unauthorized)

    tags = set()

    with get_app().test_request_context():
        for endpoint in get_app().view_functions.values():
            spec.path(view=endpoint, app=get_app())

        # Set up global tags
        spec_yaml = yaml.load(spec.to_yaml(), Loader=yaml.SafeLoader)
        for path_value in spec_yaml["paths"].values():
            for data_value in path_value.values():
                if 'tags' in data_value and any(data_value['tags']):
                    for tag in data_value['tags']:
                        tags.add(tag)
        for tag in sorted(tags):
            spec.tag({'name': tag})

        if return_tags:
            return sorted(tags)

        if format.lower() == "yaml":
            print(spec.to_yaml())
        else:
            print(json.dumps(spec.to_dict(), indent=2))
Exemplo n.º 14
0
            200:
                description: A pet to be returned
                schema: PetSchema
            400:
                x-400-suffix: yeah yeah....
    """
    pass


# Optional marshmallow support
class CategorySchema(Schema):
    id = fields.Int()
    name = fields.Str(required=True)


class PetSchema(Schema):
    category = fields.Nested(CategorySchema, many=True)
    name = fields.Str()


spec.components.schema('Category', schema=CategorySchema)
spec.components.schema('Pet', schema=PetSchema)

with app.test_request_context():
    spec.path(view=random_pet)


if __name__ == "__main__":
    # This should match extended_example_with_marshmallow_output.yaml
    print(spec.to_yaml())
Exemplo n.º 15
0
class WRAPISpec(object):
    RE_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')

    tags = [
        {
            'name': 'WASAPI (Downloads)',
            'description': 'Download WARC files API (conforms to WASAPI spec)'
        },
        {
            'name': 'Auth',
            'description': 'Auth and Login API'
        },
        {
            'name': 'Users',
            'description': 'User API'
        },
        {
            'name': 'Collections',
            'description': 'Collection API'
        },
        {
            'name': 'Recordings',
            'description': 'Recording Sessions Management API'
        },
        {
            'name': 'Lists',
            'description': 'List API'
        },
        {
            'name': 'Bookmarks',
            'description': 'Bookmarks API'
        },
        {
            'name': 'Uploads',
            'description': 'Upload WARC or HAR files API'
        },
        {
            'name': 'Add External Records',
            'description': 'Add External WARC Records API'
        },
        {
            'name': 'Browsers',
            'description': 'Browser API'
        },
        {
            'name': 'External Archives',
            'description': 'External Archives Info API'
        },
        {
            'name': 'Cookies',
            'description': 'Cookie Handling'
        },
        {
            'name': 'Bug Reporting',
            'description': 'Bug Reporting API'
        },
        {
            'name': 'Admin',
            'description': 'Admin API',
        },
        {
            'name': 'Stats',
            'description': 'Stats API',
        },
        {
            'name': 'Automation',
            'description': 'Automation API',
        },
        {
            'name': 'Behaviors',
            'description': 'Behaviors API'
        },
    ]

    # only include these groups when logged in as admin
    admin_tags = ['Admin', 'Stats', 'Automation']

    string_params = {
        'user': '******',
        'username': '******',
        'coll': 'Collection Slug',
        'coll_name': 'Collection Slug',
        'collection': 'Collection Slug',
        'rec': 'Session Id',
        'reqid': 'Remote Browser Request Id',
        'new_coll_name': 'New Collection Name',
        'list': 'List Id',
        'list_id': 'List Id',
        'bid': 'Bookmark Id',
        'autoid': 'Automation Id',
        'title': 'Title',
        'desc': 'Description',
        'url': 'Archived Url',
        'timestamp': 'Archived at Timestamp',
        'browser': 'Browser Used',
        'page_id': 'Page Id',
        'upload_id': 'Upload Id',
        'filename': 'File Name',
    }

    opt_bool_params = {
        'public': 'Publicly Accessible',
        'include_recordings': 'Include Recording Sessions in response',
        'include_lists': 'Include all lists in response',
        'include_pages': 'Include pages in response',
        'include_bookmarks': 'Include bookmarks in response',
        'public_index': 'Publicly Accessible Collection Index',
    }

    custom_params = {
        'before_id': {
            'type': 'string',
            'description': 'Insert Before this Id',
        },
        'order': {
            'type': 'array',
            'items': {
                'type': 'string'
            },
            'description': 'an array of existing ids in new order'
        }
    }

    all_responses = {
        'wasapi_list': {
            'description':
            'WASAPI response for list of WARC files available for download',
            'content': {
                'application/json': {
                    'schema': {
                        'type': 'object',
                        'properties': {
                            'files': {
                                'type': 'array',
                                'items': {
                                    'type': 'object',
                                    'properties': {
                                        'content-type': {
                                            'type': 'string'
                                        },
                                        'filetype': {
                                            'type': 'string'
                                        },
                                        'filename': {
                                            'type': 'string',
                                        },
                                        'size': {
                                            'type': 'integer'
                                        },
                                        'recording': {
                                            'type': 'string'
                                        },
                                        'recording_date': {
                                            'type': 'string'
                                        },
                                        'collection': {
                                            'type': 'string'
                                        },
                                        'checksums': {
                                            'type': 'object'
                                        },
                                        'locations': {
                                            'type': 'array',
                                            'items': {
                                                'type': 'string'
                                            }
                                        },
                                        'is_active': {
                                            'type': 'boolean'
                                        },
                                    }
                                }
                            },
                            'include-extra': {
                                'type': 'boolean'
                            }
                        }
                    }
                }
            }
        },
        'wasapi_download': {
            'description': 'WARC file',
            'content': {
                'application/warc': {
                    'schema': {
                        'type': 'string',
                        'format': 'binary',
                        'example': 'WARC/1.0\r\nWARC-Type: response\r\n...',
                    }
                }
            }
        }
    }

    @classmethod
    def bottle_path_to_openapi(cls, path):
        path_vars = cls.RE_URL.findall(path)
        path = cls.RE_URL.sub(r'{\1}', path)
        return path, path_vars

    def __init__(self, api_root):
        self.api_root = api_root
        self.api_map = defaultdict(dict)
        self.funcs = defaultdict(dict)

        self.curr_tag = ''

        self.spec = APISpec(
            title='Webrecorder',
            version='1.0.0',
            openapi_version='3.0.0',
            info=dict(
                description=
                'Webrecorder API. This API includes all features available and in use by the frontend.'
            ),
            plugins=[])

        self.admin_spec = APISpec(
            title='Webrecorder',
            version='1.0.0',
            openapi_version='3.0.0',
            info=dict(
                description=
                'Webrecorder API (including Admin). This API includes all features available in Webrecorder, including admin and stats APIs.'
            ),
            plugins=[])

        self.err_400 = self.make_err_response('Invalid Request Param')
        self.err_404 = self.make_err_response('Object Not Found')
        self.err_403 = self.make_err_response('Invalid Authorization')
        self.any_obj = self.make_any_response()

    def set_curr_tag(self, tag):
        self.curr_tag = tag

    def add_route(self, route):
        if route.rule.startswith(self.api_root):
            path, path_vars = self.bottle_path_to_openapi(route.rule)

            self.api_map[path][route.method.lower()] = route.callback

            self.funcs[route.callback]['path'] = path,
            self.funcs[route.callback]['path_params'] = self.make_params(
                path_vars, 'path')
            if self.curr_tag:
                self.funcs[route.callback]['tags'] = [self.curr_tag]

    def get_param(self, name):
        """Returns the open api description of the supplied query parameter name

        :param str name: The name of the query parameter
        :return: A dictionary containing the
        :rtype: dict
        """
        optional = name.startswith('?')
        if optional:
            name = name[1:]
        if name in self.string_params:
            param = {
                'description': self.string_params[name],
                'required': not optional,
                'schema': {
                    'type': 'string'
                },
                'name': name
            }

        elif name in self.opt_bool_params:
            param = {
                'description': self.opt_bool_params[name],
                'required': False,
                'schema': {
                    'type': 'boolean'
                },
                'name': name
            }

        elif name in self.custom_params:
            param = self.custom_params[name].copy()
            param['name'] = name

        else:
            raise AssertionError('Param {0} not found'.format(name))

        return param

    def get_req_param(self, name):
        if name in self.string_params:
            return {'type': 'string', 'description': self.string_params[name]}

        elif name in self.opt_bool_params:
            return {
                'type': 'boolean',
                'description': self.opt_bool_params[name]
            }

        elif name in self.custom_params:
            return self.custom_params[name]

        raise AssertionError('Param {0} not found'.format(name))

    def make_params(self, params, param_type):
        objs = []
        for param in params:
            obj = self.get_param(param)
            obj['in'] = param_type
            objs.append(obj)

        return objs

    def add_func(self, func, kwargs):
        query = kwargs.get('query')
        if query:
            self.funcs[func]['query_params'] = self.make_params(query, 'query')

        req = kwargs.get('req')
        if req:
            self.funcs[func]['request'] = self.get_request(
                req, kwargs.get('req_desc'))

        resp = kwargs.get('resp')
        if resp:
            self.funcs[func]['resp'] = resp

    def get_request(self, req_props, req_desc=None):
        properties = {}

        schema = None

        # make array out of props
        if isinstance(req_props, dict):
            if req_props.get('type') == 'array':
                obj_type = 'array'
                prop_list = req_props['item_type']

            assert (prop_list)

        else:
            obj_type = 'object'
            prop_list = req_props

        if not schema:
            for prop in prop_list:
                properties[prop] = self.get_req_param(prop)

            schema = {'type': 'object', 'properties': properties}

            # wrap schema in array
            if obj_type == 'array':
                schema = {'type': 'array', 'items': schema}

        request = {'content': {'application/json': {'schema': schema}}}

        if req_desc:
            request['description'] = req_desc

        return request

    def build_api_spec(self):
        for name, routes in self.api_map.items():
            ops = {}
            for method, callback in routes.items():
                info = self.funcs[callback]

                # combine path params and query params, if any
                params = info.get('path_params', []) + info.get(
                    'query_params', [])

                api = {'parameters': params}

                # for POST and PUT, generate requestBody
                if method == 'post' or method == 'put':
                    request = info.get('request')
                    if request:
                        api['requestBody'] = request
                else:
                    # otherwise, ensure no request body!
                    assert 'request' not in info

                # set tags, if any
                if 'tags' in info:
                    api['tags'] = info['tags']
                    is_admin = info['tags'][0] in self.admin_tags

                api['responses'] = self.get_responses(info.get('resp', None))

                ops[method] = api

            if not is_admin:
                self.spec.add_path(path=name, operations=ops)

            self.admin_spec.add_path(path=name, operations=ops)

        for tag in self.tags:
            self.admin_spec.add_tag(tag)

            if tag['name'] not in self.admin_tags:
                self.spec.add_tag(tag)
            else:
                print('skip', tag)

    def get_responses(self, obj_type):
        response_obj = self.all_responses.get(obj_type) or self.any_obj
        obj = {'400': self.err_400, '404': self.err_404, '200': response_obj}

        return obj

    def make_err_response(self, msg):
        obj = {
            'description': msg,
            'content': {
                'application/json': {
                    'schema': {
                        'type': 'object',
                        'properties': {
                            'error': {
                                'type': 'string'
                            }
                        }
                    }
                }
            }
        }

        return obj

    def make_any_response(self):
        obj = {
            'description': 'Any Object',
            'content': {
                'application/json': {
                    'schema': {
                        'type': 'object',
                        'additionalProperties': True
                    }
                }
            }
        }

        return obj

    def get_api_spec_yaml(self, use_admin=False):
        """Returns the api specification as a yaml string

        :return: The api specification as a yaml string
        :rtype: str
        """
        return self.spec.to_yaml(
        ) if not use_admin else self.admin_spec.to_yaml()

    def get_api_spec_dict(self, use_admin=False):
        """Returns the api specification as a dictionary

        :return: The api specification as a dictionary
        :rtype: dict
        """
        return self.spec.to_dict(
        ) if not use_admin else self.admin_spec.to_dict()
def generate(app, api_commit_hash, api_semantic_version):

    ###########################
    ##### API SPEC ############
    # API spec is autogenerated using the 'api-spec' library, and saved in the project root
    # as well as being served on the root '/' endpoint for consumption by services
    spec = APISpec(
        title="RCPCH Digital Growth Charts API",
        version=f'v{api_semantic_version} (commit_hash: {api_commit_hash})',
        openapi_version="3.0.2",
        info=dict(
            description=
            "Royal College of Paediatrics and Child Health Digital Growth Charts",
            license={
                "name": "GNU Affero General Public License",
                "url": "https://www.gnu.org/licenses/agpl-3.0.en.html"
            }),
        plugins=[MarshmallowPlugin(), FlaskPlugin()],
        servers=[{
            "url":
            'https://api.rcpch.ac.uk',
            "description":
            'RCPCH Production API Gateway (subscription keys required)'
        }, {
            "url": 'https://localhost:5000',
            "description": 'Your local development API'
        }],
    )

    spec.components.schema("uk_who_calculation",
                           schema=schemas.CalculationResponseSchema)
    with app.test_request_context():
        spec.path(view=blueprints.uk_who_blueprint.uk_who_calculation)

    spec.components.schema("chartData", schema=schemas.ChartDataResponseSchema)
    with app.test_request_context():
        spec.path(view=blueprints.uk_who_blueprint.uk_who_chart_coordinates)

    spec.components.schema("plottableChildData",
                           schema=schemas.PlottableChildDataResponseSchema)
    with app.test_request_context():
        spec.path(view=blueprints.uk_who_blueprint.uk_who_plottable_child_data)

    spec.components.schema("references",
                           schema=schemas.ReferencesResponseSchema)
    with app.test_request_context():
        spec.path(view=blueprints.utilities_blueprint.references)

    # Instructions endpoint (TODO: #121 #120 candidate for deprecation)
    with app.test_request_context():
        spec.path(view=blueprints.utilities_blueprint.instructions)

    # Trisomy 21 endpoint
    spec.components.schema("trisomy_21_calculation",
                           schema=schemas.CalculationResponseSchema)
    with app.test_request_context():
        spec.path(view=blueprints.trisomy_21_blueprint.trisomy_21_calculation)

    # OpenAPI3 specification endpoint
    with app.test_request_context():
        spec.path(view=blueprints.openapi_blueprint.openapi_endpoint)

    # Turner's syndrome endpoint
    spec.components.schema("turner_calculation",
                           schema=schemas.CalculationResponseSchema)
    with app.test_request_context():
        spec.path(view=blueprints.turner_blueprint.turner_calculation)

    ##### END API SPEC ########
    ###########################

    ################################
    ### API SPEC AUTO GENERATION ###

    # Create OpenAPI Spec and serialise it to file
    with open(r'openapi.yml', 'w') as file:
        file.write(spec.to_yaml())

    with open(r'openapi.json', 'w') as file:
        file.write(json.dumps(spec.to_dict(), sort_keys=True, indent=4))
    ### END API SPEC AUTO GENERATION ###
    ####################################

    return spec
Exemplo n.º 17
0
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin

from model_service import __doc__
from model_service.schemas import *
from model_service.endpoints import *

spec = APISpec(
    openapi_version="3.0.2",
    title='Model Service',
    version='0.1.0',
    info=dict(description=__doc__),
    plugins=[FlaskPlugin(), MarshmallowPlugin()],
)

spec.components.schema("ModelSchema", schema=ModelSchema)
spec.components.schema("ModelCollectionSchema", schema=ModelCollectionSchema)
spec.components.schema("JsonSchemaProperty", schema=JsonSchemaProperty)
spec.components.schema("JSONSchema", schema=JSONSchema)
spec.components.schema("ModelMetadataSchema", schema=ModelMetadataSchema)
spec.components.schema("ErrorSchema", schema=ErrorSchema)

with app.test_request_context():
    spec.path(view=get_models)
    spec.path(view=get_metadata)
    spec.path(view=predict)

with open('../openapi_specification.yaml', 'w') as f:
    f.write(spec.to_yaml())
Exemplo n.º 18
0
class Spec(object):
    title = 'Swagman'
    version = '1.0.0'
    description = 'A sample description'
    openapi_version = '3.0.0'

    def __init__(self, ignoreschema=None, **options):
        self.spec = APISpec(title=self.title,
                            version=self.version,
                            openapi_version=self.openapi_version,
                            plugins=[],
                            **options)
        self.ignoreschema = ignoreschema
        self._counter = {'example': {}, 'schema': {}}
        self._examples = {}

    def set_title(self, title=None):
        self.spec.title = title or self.title

    def set_version(self, version=None):
        self.spec.version = version or self.version

    def set_description(self, description=''):
        self.spec.options['info'] = dict(
            description=(description or self.description))

    def add_component_response(self, name, schema):
        self.spec.components.response(name, schema)
        return self

    def add_component_example(self, name, schema):
        if self._counter['example'].get(name, None) is None:
            self._counter['example'] = {name: 0}
            _name = name
        else:
            self._counter['example'][name] += 1
            _name = name + '_' + str(self._counter['example'][name])
        try:
            self.spec.components.example(_name, schema)
        except DuplicateComponentNameError as e:
            # new schema has same repr?
            self.add_component_example(name, schema)
        return self

    def add_component_schema(self, name, schema):
        if self._counter['schema'].get(name, None) is None:
            self._counter['schema'] = {name: 0}
            _name = name
        else:
            self._counter['schema'][name] += 1
            _name = name + '_' + str(self._counter['schema'][name])
        try:
            self.spec.components.schema(_name, schema)
        except DuplicateComponentNameError as e:
            # new schema has same repr?
            self.add_component_schema(name, schema)
        return self

    def get_params(self, request):
        requestparams = []
        params = request.getParams()

        for location, param in params.items():
            for eachparam in param:
                schema = dict(type='string')
                if eachparam.get('type', None) is not None:
                    schema['type'] = postman_to_openapi_typemap.get(
                        eachparam.get('type'), 'string')
                if eachparam.get('value', None) is not None:
                    schema['default'] = eachparam.get('value')
                    schema['example'] = eachparam.get('value')
                requestparams.append({
                    "in": location,
                    "name": eachparam.get('name', ''),
                    "schema": schema
                })
        return requestparams

    def json_get_path(self, match):
        '''return an iterator based upon MATCH.PATH. Each item is a path component,
    start from outer most item.'''
        if match.context is not None:
            for path_element in self.json_get_path(match.context):
                yield path_element
            yield str(match.path)

    def json_update_path(self, json, path, value):
        '''Update JSON dictionnary PATH with VALUE. Return updated JSON'''
        try:
            first = next(path)
            # check if item is an array
            if first.startswith('[') and first.endswith(']'):
                try:
                    first = int(first[1:-1])
                except ValueError:
                    pass
            json[first] = self.json_update_path(json[first], path, value)
            return json
        except StopIteration:
            return value

    def getFilters(self, path, method, code):
        if not len(self.ignoreschema.keys()):
            return []
        for _path, schemas in self.ignoreschema['schema'].items():
            if PostmanParser.camelize(_path) == path:
                for _method, responsecode in schemas.items():
                    if _method == method:
                        return responsecode.get(code, [])
        return []

    def parse_skip(self, expr):
        type_explode = expr.split(':')
        if len(type_explode) > 1 and type_explode[-1] == 'a':
            return ''.join(type_explode[:-1]), True
        return expr, False

    def filterResponse(self, path, method, code, response):
        responsejson = response.getBody()
        filters = self.getFilters(path, method, code)
        if len(filters) > 0:
            for jsonfilter in filters:
                expr, skip = self.parse_skip(jsonfilter)
                expr = expr if expr else jsonfilter
                jsonpath_expr = jsonpath_rw.parse(expr)
                matches = jsonpath_expr.find(responsejson)
                ignoreprop = PostmanParser.IGNOREPROPKEYVAL if skip else PostmanParser.IGNOREPROP
                for match in matches:
                    responsejson = self.json_update_path(
                        responsejson, self.json_get_path(match), ignoreprop)
        return responsejson

    def get_operations(self, item):
        operations = dict(
            get=dict(responses=dict()),
            post=dict(responses=dict()),
            put=dict(responses=dict()),
            delete=dict(responses=dict()),
            head=dict(responses=dict()),
        )
        camelizeKey = PostmanParser.camelize(
            item['request'].getPathNormalised())
        requestbody = item['request'].getBody()
        requestbodyschema = PostmanParser.schemawalker(requestbody)
        requestbodytype = item['request'].getBodyContent()
        for response in item['responses']:
            if not item['request'].getBody():
                requestbody = response.getRequestBody()
                requestbodyschema = PostmanParser.schemawalker(requestbody)
                requestbodytype = response.getRequestHeader('Content-Type')

            code = response.getCode()
            reqtype = response.getMethod().lower()
            responseBody = self.filterResponse(camelizeKey, reqtype, code,
                                               response)
            responseSchema = PostmanParser.schemawalker(responseBody)
            camelizeKeyExample = PostmanParser.camelize(response.getName())
            ref = self.add_component_schema((camelizeKey + str(code)),
                                            responseSchema)
            if requestbody:
                self.set_example(('request' + camelizeKey), camelizeKeyExample,
                                 dict(value=requestbody))
            self.set_example(
                ('response' + camelizeKey + str(code)), camelizeKeyExample,
                dict(value=response.getBody()))
            operations[reqtype]['operationId'] = camelizeKey + reqtype
            operations[reqtype]['parameters'] = self.get_params(
                item['request'])
            if requestbody:
                operations[reqtype]['requestBody'] = dict(
                    content={
                        requestbodytype:
                        dict(schema=requestbodyschema,
                             examples=self.get_example((
                                 'request' + camelizeKey), camelizeKeyExample))
                    })
            operations[reqtype]['responses'][code] = {
                'description': response.getName(),
                'content': {
                    response.getHeader('Content-Type'): {
                        "schema":
                        self.get_ref('schema', (camelizeKey + str(code))),
                        "examples":
                        self.get_example(
                            ('response' + camelizeKey + str(code)),
                            camelizeKeyExample)
                    }
                }
            }

        # Reset schema counter
        self._counter = {'example': {}, 'schema': {}}
        # Return new dict copy from original, containing only filled responses
        # since python3 doesn't allow mutating dict during iteration, that's
        # the best I can do currently.
        #@TODO fix this please
        newdict = dict()
        for k, v in operations.items():
            if v['responses']:
                newdict[k] = v
        return newdict

    def set_example(self, responseKey, exampleKey, exampleBody):
        if responseKey in self._examples:
            self._examples[responseKey][exampleKey] = exampleBody
        else:
            self._examples[responseKey] = {exampleKey: exampleBody}
        self.add_component_example((responseKey + exampleKey), exampleBody)

    def get_example(self, responseKey, exampleKey):
        examples = self._examples[responseKey]
        for exampleKey, example in examples.items():
            ref = self.get_ref('example', (responseKey + exampleKey))
            examples[exampleKey] = ref
        return examples

    def get_ref(self, holder, name):
        refs = self.spec.get_ref(holder, name)
        counter = self._counter[holder].get(name, 0)
        if counter > 0:
            refs = dict(oneOf=[refs])
            for i in range(1, (counter + 1)):
                ref = self.spec.get_ref(holder, name + '_' + str(i))
                refs['oneOf'].append(ref)
        return refs

    def add_item(self, item):
        operations = self.get_operations(item)
        self.spec.path(path=item['request'].getPathNormalised(),
                       operations=operations)
        return self

    def to_dict(self):
        return self.spec.to_dict()

    def to_yaml(self):
        return self.spec.to_yaml()
Exemplo n.º 19
0
class WRAPISpec(object):
    RE_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')

    tags = [
        {
            'name': 'Auth',
            'description': 'Auth and Login API'
        },
        {
            'name': 'Users',
            'description': 'User API'
        },
        {
            'name': 'Collections',
            'description': 'Collection API'
        },
        {
            'name': 'Recordings',
            'description': 'Recording Sessions Management API'
        },
        {
            'name': 'Lists',
            'description': 'List API'
        },
        {
            'name': 'Bookmarks',
            'description': 'Bookmarks API'
        },
        {
            'name': 'Browsers',
            'description': 'Browser API'
        },
        {
            'name': 'External Archives',
            'description': 'External Archives Info API'
        },
        {
            'name': 'Cookies',
            'description': 'Cookie Handling'
        },
        {
            'name': 'Bug Reporting',
            'description': 'Bug Reporting API'
        },
        {
            'name': 'Admin',
            'description': 'Admin API'
        },
        {
            'name': 'Stats',
            'description': 'Stats API'
        },
    ]

    string_params = {
        'user': '******',
        'username': '******',
        'coll': 'Collection Slug',
        'coll_name': 'Collection Slug',
        'rec': 'Session Id',
        'reqid': 'Remote Browser Request Id',
        'new_coll_name': 'New Collection Name',
        'list': 'List Id',
        'list_id': 'List Id',
        'bid': 'Bookmark Id',
        'title': 'Title',
        'desc': 'Description',
        'url': 'Archived Url',
        'timestamp': 'Archived at Timestamp',
        'browser': 'Browser Used',
        'page_id': 'Page Id',
        'upload_id': 'Upload Id',
    }

    opt_bool_params = {
        'public': 'Publicly Accessible',
        'include_recordings': 'Include Recording Sessions in response',
        'include_lists': 'Include all lists in response',
        'include_pages': 'Include pages in response',
        'include_bookmarks': 'Include bookmarks in response',
        'public_index': 'Publicly Accessible Collection Index',
    }

    custom_params = {
        'before_id': {
            'type': 'string',
            'description': 'Insert Before this Id',
        },
        'order': {
            'type': 'array',
            'items': {
                'type': 'string'
            },
            'description': 'an array of existing ids in new order'
        }
    }

    all_responses = {}

    @classmethod
    def bottle_path_to_openapi(cls, path):
        path_vars = cls.RE_URL.findall(path)
        path = cls.RE_URL.sub(r'{\1}', path)
        return path, path_vars

    def __init__(self, api_root):
        self.api_root = api_root
        self.api_map = defaultdict(dict)
        self.funcs = defaultdict(dict)

        self.curr_tag = ''

        self.spec = APISpec(title='Webrecorder',
                            version='1.0.0',
                            openapi_version='3.0.0',
                            info=dict(description='Webrecorder API'),
                            plugins=[])

        self.err_400 = self.make_err_response('Invalid Request Param')
        self.err_404 = self.make_err_response('Object Not Found')
        self.err_403 = self.make_err_response('Invalid Authorization')
        self.any_obj = self.make_any_response()

    def set_curr_tag(self, tag):
        self.curr_tag = tag

    def add_route(self, route):
        if route.rule.startswith(self.api_root):
            path, path_vars = self.bottle_path_to_openapi(route.rule)

            self.api_map[path][route.method.lower()] = route.callback

            self.funcs[route.callback]['path'] = path,
            self.funcs[route.callback]['path_params'] = self.make_params(
                path_vars, 'path')
            if self.curr_tag:
                self.funcs[route.callback]['tags'] = [self.curr_tag]

    def get_param(self, name):
        if name in self.string_params:
            param = {
                'description': self.string_params[name],
                'required': True,
                'schema': {
                    'type': 'string'
                },
                'name': name
            }

        elif name in self.opt_bool_params:
            param = {
                'description': self.opt_bool_params[name],
                'required': False,
                'schema': {
                    'type': 'boolean'
                },
                'name': name
            }

        elif name in self.custom_params:
            param = self.custom_params[name].copy()
            param['name'] = name

        else:
            raise AssertionError('Param {0} not found'.format(name))

        return param

    def get_req_param(self, name):
        if name in self.string_params:
            return {'type': 'string', 'description': self.string_params[name]}

        elif name in self.opt_bool_params:
            return {
                'type': 'boolean',
                'description': self.opt_bool_params[name]
            }

        elif name in self.custom_params:
            return self.custom_params[name]

        raise AssertionError('Param {0} not found'.format(name))

    def make_params(self, params, param_type):
        objs = []
        for param in params:
            obj = self.get_param(param)
            obj['in'] = param_type
            objs.append(obj)

        return objs

    def add_func(self, func, kwargs):
        query = kwargs.get('query')
        if query:
            self.funcs[func]['query_params'] = self.make_params(query, 'query')

        req = kwargs.get('req')
        if req:
            self.funcs[func]['request'] = self.get_request(
                req, kwargs.get('req_desc'))

    def get_request(self, req_props, req_desc=None):
        properties = {}

        schema = None

        # make array out of props
        if isinstance(req_props, dict):
            if req_props.get('type') == 'array':
                obj_type = 'array'
                prop_list = req_props['item_type']

            assert (prop_list)

        else:
            obj_type = 'object'
            prop_list = req_props

        if not schema:
            for prop in prop_list:
                properties[prop] = self.get_req_param(prop)

            schema = {'type': 'object', 'properties': properties}

            # wrap schema in array
            if obj_type == 'array':
                schema = {'type': 'array', 'items': schema}

        request = {'content': {'application/json': {'schema': schema}}}

        if req_desc:
            request['description'] = req_desc

        return request

    def build_api_spec(self):
        for name, routes in self.api_map.items():
            ops = {}
            for method, callback in routes.items():
                info = self.funcs[callback]

                # combine path params and query params, if any
                params = info.get('path_params', []) + info.get(
                    'query_params', [])

                api = {'parameters': params}

                # for POST and PUT, generate requestBody
                if method == 'post' or method == 'put':
                    request = info.get('request')
                    if request:
                        api['requestBody'] = request
                else:
                    # otherwise, ensure no request body!
                    assert 'request' not in info

                # set tags, if any
                if 'tags' in info:
                    api['tags'] = info['tags']

                api['responses'] = self.get_responses(None)

                ops[method] = api

            self.spec.add_path(path=name, operations=ops)

        for tag in self.tags:
            self.spec.add_tag(tag)

    def get_responses(self, obj_type):
        response_obj = self.all_responses.get(obj_type) or self.any_obj
        obj = {'400': self.err_400, '404': self.err_404, '200': response_obj}

        return obj

    def make_err_response(self, msg):
        obj = {
            'description': msg,
            'content': {
                'application/json': {
                    'schema': {
                        'type': 'object',
                        'properties': {
                            'error': {
                                'type': 'string'
                            }
                        }
                    }
                }
            }
        }

        return obj

    def make_any_response(self):
        obj = {
            'description': 'Any Object',
            'content': {
                'application/json': {
                    'schema': {
                        'type': 'object',
                        'additionalProperties': True
                    }
                }
            }
        }

        return obj

    def get_api_spec_yaml(self):
        return self.spec.to_yaml()
Exemplo n.º 20
0
    },
)

specification.path(
    path="/orders/{order_id}",
    parameters=[make_parameter(in_="path", name="order_id", schema={"type": "string"})],
    operations={
        "get": {
            "summary": "Returns the details of a specific order",
            "responses": make_response(
                "GetOrder", description="A JSON representation of an order"
            ),
        },
        "put": {
            "description": "Replaces an existing order",
            "requestBody": make_request_body("CreateOrder"),
            "responses": make_response(
                "GetOrder", description="A JSON representation of an order"
            ),
        },
        "delete": {
            "description": "Deletes an existing order",
            "responses": {
                "204": {"description": "The resource was deleted successfully"}
            },
        },
    },
)

print(specification.to_yaml())