Exemplo n.º 1
0
    def get_specification(self):
        spec = APISpec(
            title=self.__class__.__name__,
            version='1.0.0',
            plugins=(
                'apispec.ext.flask',
                'apispec.ext.marshmallow',
            ),
        )

        class PetSchema(Schema):
            id = fields.Int()
            name = fields.Str()

        app = Flask(__name__)

        @app.route('/random')
        def random_pet():
            """A cute furry animal endpoint.
            ---
            get:
                description: Get a random pet
                responses:
                    200:
                        description: A pet to be returned
                        schema: PetSchema
            """
            return jsonify({})

        # Register entities and paths
        spec.definition('Pet', schema=PetSchema)
        with app.test_request_context():
            spec.add_path(view=random_pet)

        return json.dumps(spec.to_dict(), indent=4)
Exemplo n.º 2
0
def spec(app, schemas, routes):
    spec = APISpec(
        title='test api',
        version='0.1.0',
        plugins=('apispec.ext.marshmallow', 'flask_resty.spec'))

    spec.definition('Foo', schema=schemas['foo'])

    spec.add_path(view=routes['fooList'])
    spec.add_path(view=routes['foo'])
    spec.add_path(view=routes['bar'])

    return spec.to_dict()
Exemplo n.º 3
0
    def get_specification(self):
        spec = APISpec(
            title=self.__class__.__name__,
            version='1.0.0',
            plugins=(
                'apispec.ext.flask',
                'apispec.ext.marshmallow',
            ),
        )

        class PetParameter(Schema):
            pet_id = fields.Int()

        class PetSchema(Schema):
            id = fields.Int()
            name = fields.Str()

        app = Flask(__name__)

        @app.route('/pets/<int:pet_id>')
        def get_pet(pet_id):
            """A cute furry animal endpoint.
            ---
            get:
                description: Get a random pet
                parameters:
                    - in: path
                      schema: PetParameter
                responses:
                    200:
                        description: A pet to be returned
                        schema: PetSchema
            """
            return jsonify({})

        # Register entities and paths
        spec.definition('Pet', schema=PetSchema)
        spec.definition('PetParameter', schema=PetParameter, required=True)
        with app.test_request_context():
            spec.add_path(view=get_pet)

        specification_as_string = json.dumps(spec.to_dict(), indent=4)

        # Kludge! I was unable to do this via `apispec`
        specification_as_string = specification_as_string.replace('"required": false,',
                                                                  '"required": true,')

        return specification_as_string
Exemplo n.º 4
0
    def get_specification(self):
        spec = APISpec(
            title=self.__class__.__name__,
            version='1.0.0',
            plugins=(
                'apispec.ext.flask',
                'apispec.ext.marshmallow',
            ),
        )

        class A(Schema):
            id = fields.Int()
            pointers = fields.Nested('B', many=True)

        class B(Schema):
            id = fields.Int()
            pointers = fields.Nested('C', many=True)

        class C(Schema):
            id = fields.Int()
            pointers = fields.Nested('A', many=True)

        app = Flask(__name__)

        @app.route('/random')
        def random_pet():
            """A cute furry animal endpoint.
            ---
            get:
                description: Get a random pet
                responses:
                    200:
                        description: A pet to be returned
                        schema: PetSchema
            """
            return jsonify({})

        # Register entities and paths
        spec.definition('A', schema=A)
        spec.definition('B', schema=B)
        spec.definition('C', schema=C)
        with app.test_request_context():
            spec.add_path(view=random_pet)

        return json.dumps(spec.to_dict(), indent=4)
Exemplo n.º 5
0
    def _apispec(self):
        spec = APISpec(
            title=self.title,
            version=self.version,
            openapi_version=self.openapi_version,
            plugins=[MarshmallowPlugin()],
        )

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

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

        return spec
Exemplo n.º 6
0
    def test_resolve_schema_dict_auto_reference_in_list(
            self, schema, deprecated_interface):
        def resolver(schema):
            return schema.__name__

        if deprecated_interface:
            spec = APISpec(
                title='Test auto-reference',
                version='0.1',
                description='Test auto-reference',
                plugins=('apispec.ext.marshmallow', ),
                schema_name_resolver=resolver,
            )
        else:
            spec = APISpec(
                title='Test auto-reference',
                version='0.1',
                description='Test auto-reference',
                plugins=(MarshmallowPlugin(schema_name_resolver=resolver, ), ),
            )
        assert {} == spec._definitions

        spec.definition('analysis', schema=schema)
        spec.add_path(
            '/test',
            operations={
                'get': {
                    'responses': {
                        '200': {
                            'schema': {
                                '$ref': '#/definitions/analysis',
                            },
                        },
                    },
                },
            },
        )

        assert 3 == len(spec._definitions)

        assert 'analysis' in spec._definitions
        assert 'SampleSchema' in spec._definitions
        assert 'RunSchema' in spec._definitions
Exemplo n.º 7
0
def api_spec(request):
    """
    Serve the spec to explorer
    """
    spec = APISpec(title="Some API",
                   version="1.0.0",
                   plugins=["apispec.ext.marshmallow"])
    # using marshmallow plugin here
    spec.definition("FooBodySchema", schema=validation.FooBodySchema)
    spec.definition("BarBodySchema",
                    schema=validation.BarBodySchema(many=True))

    # inspect the `foo_route` and generate operations from docstring
    add_pyramid_paths(spec, "users", request=request)

    # inspection supports filtering via pyramid add_view predicate arguments
    add_pyramid_paths(spec, "bar_route", request=request)
    my_spec = spec.to_dict()
    # you can further mutate the spec dict to include things like security definitions
    return my_spec
Exemplo n.º 8
0
def build_openapi_spec():
    """Create OpenAPI definition."""
    spec = APISpec(title='reana-job-controller',
                   version='0.4.0',
                   info=dict(description='REANA Job Controller API'),
                   plugins=[
                       'apispec.ext.flask',
                       'apispec.ext.marshmallow',
                   ])

    # Add marshmallow models to specification
    spec.definition('Job', schema=Job)
    spec.definition('JobRequest', schema=JobRequest)

    # Collect OpenAPI docstrings from Flask endpoints
    for key in current_app.view_functions:
        if key != 'static' and key != 'get_openapi_spec':
            spec.add_path(view=current_app.view_functions[key])

    return spec.to_dict()
Exemplo n.º 9
0
    def test_resolve_schema_dict_auto_reference_return_none(self, schema):
        # this resolver return None
        def resolver(schema):
            return None

        spec = APISpec(
            title='Test auto-reference',
            version='0.1',
            description='Test auto-reference',
            plugins=(MarshmallowPlugin(schema_name_resolver=resolver, ), ),
        )
        assert {} == spec._definitions

        spec.definition('analysis', schema=schema)
        spec.add_path(
            '/test',
            operations={
                'get': {
                    'responses': {
                        '200': {
                            'schema': {
                                '$ref': '#/definitions/analysis',
                            },
                        },
                    },
                },
            },
        )

        # Other shemas not yet referenced
        assert 1 == len(spec._definitions)

        spec_dict = spec.to_dict()
        assert spec_dict.get('definitions')
        assert 'analysis' in spec_dict['definitions']
        # Inspect/Read objects will not auto reference because resolver func
        # return None
        json.dumps(spec_dict)
        # Other shema still not referenced
        assert 1 == len(spec._definitions)
Exemplo n.º 10
0
    def test_resolve_schema_dict_auto_reference_in_list(self, schema):
        def resolver(schema):
            return schema.__name__

        def class_resolver(spec, schema):
            if isinstance(schema, type):
                return schema
            else:
                return type(schema)

        spec = APISpec(
            title='Test auto-reference',
            version='2.0',
            description='Test auto-reference',
            plugins=('apispec.ext.marshmallow', ),
            schema_name_resolver=resolver,
            schema_class_resolver=class_resolver,
            auto_referencing=True,
        )
        assert {} == spec._definitions

        spec.definition('analysis', schema=schema)
        spec.add_path('/test',
                      operations={
                          'get': {
                              'responses': {
                                  '200': {
                                      'schema': {
                                          '$ref': '#/definitions/analysis'
                                      }
                                  }
                              }
                          }
                      })

        assert 3 == len(spec._definitions)

        assert 'analysis' in spec._definitions
        assert 'SampleSchema' in spec._definitions
        assert 'RunSchema' in spec._definitions
Exemplo n.º 11
0
def generate_specs(module_name, **options):
    module = import_service_module(module_name)
    service_name = ''.split('.')[-1]

    ctx = module.app.test_request_context()
    ctx.push()
    spec = APISpec(title=service_name,
                   version='1.0.0',
                   plugins=(FlaskRestyPlugin(), ),
                   **options)

    schemas = get_subclasses(module.schemas, Schema)
    for schema in schemas:
        spec.definition(prettify_schema_name(schema), schema=schema)

    for view in get_subclasses(module.views, ApiView):
        try:
            spec.add_path(view=view, tag=True)
        except KeyError:
            pass  # means that the view is not registered. move along

    return json.dumps(spec.to_dict(), indent=2)
Exemplo n.º 12
0
def build_openapi_spec():
    """Create OpenAPI definition."""
    spec = APISpec(
        title="reana-job-controller",
        version=__version__,
        info=dict(description="REANA Job Controller API"),
        plugins=[
            "apispec.ext.flask",
            "apispec.ext.marshmallow",
        ],
    )

    # Add marshmallow models to specification
    spec.definition("Job", schema=Job)
    spec.definition("JobRequest", schema=JobRequest)

    # Collect OpenAPI docstrings from Flask endpoints
    for key in current_app.view_functions:
        if key != "static" and key != "get_openapi_spec":
            spec.add_path(view=current_app.view_functions[key])

    return spec.to_dict()
Exemplo n.º 13
0
def generate_specs(module_name):
    module = import_service_module(module_name)
    service_name = ''.split('.')[-1]

    ctx = module.app.test_request_context()
    ctx.push()
    spec = APISpec(
        title=service_name,
        version='1.0.0',
        plugins=('apispec.ext.marshmallow', 'flask_resty.spec'))

    schemas = get_subclasses(module.schemas, Schema)
    for schema in schemas:
        spec.definition(prettify_schema_name(schema), schema=schema)

    for view in get_subclasses(module.views, ApiView):
        try:
            spec.add_path(view=view, tag=True)
        except KeyError:
            pass  # means that the view is not registered. move along

    return json.dumps(spec.to_dict(), indent=2)
Exemplo n.º 14
0
def test_marshmallow_integration(app):
    spec = APISpec(
        title='Swagger Petstore',
        version='1.0.0',
        plugins=['apispec_chalice', 'apispec.ext.marshmallow'],
    )

    @app.route('/gists/{gist_id}', methods=['GET'])
    def gist_detail(gist_id):
        '''
        ---
        get:
            responses:
                200:
                    schema: GistSchema
        '''
        pass

    class GistSchema(Schema):
        id = fields.Int()
        name = fields.Str(required=True)

    spec.definition('Gist', schema=GistSchema)
    spec.add_path(app=app, view=gist_detail)

    assert '/gists/{gist_id}' in spec._paths
    assert 'get' in spec._paths['/gists/{gist_id}']
    assert spec._paths['/gists/{gist_id}']['get'] == {
        'responses': {
            200: {
                'schema': {
                    '$ref': '#/definitions/Gist'
                }
            }
        }
    }
Exemplo n.º 15
0
class RestApi(object):
    def __init__(self, app=None, title='Sample API', version='0.1.0'):
        self._spec = APISpec(title=title,
                             version=version,
                             openapi_version='2.0',
                             plugins=(FlaskPlugin(), MarshmallowPlugin()))
        self._routes = {}
        self._schemas = {}

        self.app = None
        self.blueprint = None

        if app is not None:
            self.app = app
            self.init_app(app)

    # -----------------------------------------------------------------------------------
    # Flask Extension Initialization
    # -----------------------------------------------------------------------------------

    def swagger_json(self):
        return jsonify(self.generate_spec().to_dict())

    def swagger_ui(self):
        """ Render a SwaggerUI for a given API """
        return render_template('swagger-ui.html')

    def init_app(self, app):
        # If app is a blueprint, defer initialization
        try:
            app.record(self._deferred_blueprint_init)
        # Flask.Blueprint has a 'record' attribute, Flask.Api does not
        except AttributeError:
            self._init_app(app)
        else:
            self.blueprint = app

    def _init_app(self, app):
        spec_blueprint = Blueprint(
            'flask-restbuilder',
            __name__,
            static_folder='./static',
            template_folder='./templates',
            static_url_path='/flask-restbuilder/static',
        )

        spec_blueprint.add_url_rule('/swagger/', 'swagger-json',
                                    self.swagger_json)
        spec_blueprint.add_url_rule('/swagger-ui/', 'swagger-ui',
                                    self.swagger_ui)
        app.register_blueprint(spec_blueprint)

    def _deferred_blueprint_init(self):
        pass

    # -----------------------------------------------------------------------------------
    # Properties
    # -----------------------------------------------------------------------------------

    @property
    def routes(self):
        return self._routes.values()

    @property
    def schemas(self):
        return self._schemas.values()

    # -----------------------------------------------------------------------------------
    # Class Methods
    # -----------------------------------------------------------------------------------

    def add_route(self, callback, url):
        if getattr(self._routes, url, None):
            raise KeyError('Trying to Map Two Route Handlers to One Path')
        self._routes[url] = callback

    def add_schema(self, schema, name):
        if getattr(self._schemas, name, None):
            raise KeyError('Trying to Map Two Route Handlers to One Path')
        self._schemas[name] = schema

    def generate_spec(self):
        for name, schema in self._schemas.items():
            self._spec.definition(name, schema=schema)

        with self.app.test_request_context():
            for _, callback in self._routes.items():
                self._spec.add_path(view=callback)

        return self._spec

    # -----------------------------------------------------------------------------------
    # API Decorators (wrapping class methods)
    # -----------------------------------------------------------------------------------

    def route(self, url):
        def wrapper(callback):
            self.add_route(callback, url)
            self.app.add_url_rule(url, callback.__name__, callback)
            return callback

        return wrapper

    def schema(self, name=None):
        def wrapper(cls):
            self.add_schema(cls, cls.__name__ if not name else name)
            return cls

        return wrapper
Exemplo n.º 16
0
class ApiSpecPlugin(BasePlugin, DocBlueprintMixin):
    """Плагин для связки json_api и swagger"""

    def __init__(self, app=None, spec_kwargs=None, decorators=None, tags: Dict[str, str] = None):
        """

        :param spec_kwargs:
        :param decorators:
        :param tags: {'<name tag>': '<description tag>'}
        """
        self.decorators_for_autodoc = decorators or tuple()
        self.spec_kwargs = spec_kwargs if spec_kwargs is not None else {}
        self.spec = None
        self.spec_tag = {}
        self.spec_schemas = {}
        self.app = None
        self._fields = []
        # Use lists to enforce order
        self._fields = []
        self._converters = []

        # Инициализация ApiSpec
        self.app = app
        self._app = app
        # Initialize spec
        openapi_version = app.config.get("OPENAPI_VERSION", "2.0")
        openapi_major_version = int(openapi_version.split(".")[0])
        if openapi_major_version < 3:
            base_path = app.config.get("APPLICATION_ROOT")
            # Don't pass basePath if '/' to avoid a bug in apispec
            # https://github.com/marshmallow-code/apispec/issues/78#issuecomment-431854606
            # TODO: Remove this condition when the bug is fixed
            if base_path != "/":
                self.spec_kwargs.setdefault("basePath", base_path)
        self.spec_kwargs.update(app.config.get("API_SPEC_OPTIONS", {}))
        self.spec = APISpec(
            app.name,
            app.config.get("API_VERSION", "1"),
            openapi_version=openapi_version,
            plugins=[MarshmallowPlugin(), RestfulPlugin()],
            **self.spec_kwargs,
        )

        tags = tags if tags else {}
        for tag_name, tag_description in tags.items():
            self.spec_tag[tag_name] = {"name": tag_name, "description": tag_description, "add_in_spec": False}
            self._add_tags_in_spec(self.spec_tag[tag_name])

    def after_init_plugin(self, *args, app=None, **kwargs):
        # Register custom fields in spec
        for args in self._fields:
            self.spec.register_field(*args)
        # Register custom converters in spec
        for args in self._converters:
            self.spec.register_converter(*args)

        # Initialize blueprint serving spec
        self._register_doc_blueprint()

    def after_route(
        self,
        resource: Union[ResourceList, ResourceDetail] = None,
        view=None,
        urls: Tuple[str] = None,
        self_json_api: Api = None,
        tag: str = None,
        default_parameters=None,
        default_schema: Schema = None,
        **kwargs,
    ) -> None:
        """

        :param resource:
        :param view:
        :param urls:
        :param self_json_api:
        :param str tag: тег под которым стоит связать этот ресурс
        :param default_parameters: дефолтные поля для ресурса в сваггер (иначе просто инициализируется [])
        :param Schema default_schema: схема, которая подставиться вместо схемы в стили json api
        :param kwargs:
        :return:
        """
        # Register views in API documentation for this resource
        # resource.register_views_in_doc(self._app, self.spec)
        # Add tag relative to this resource to the global tag list

        # We add definitions (models) to the apiscpec
        if resource.schema:
            self._add_definitions_in_spec(resource.schema)

        # We add tags to the apiscpec
        tag_name = view.title()
        if tag is None and view.title() not in self.spec_tag:
            dict_tag = {"name": view.title(), "description": "", "add_in_spec": False}
            self.spec_tag[dict_tag["name"]] = dict_tag
            self._add_tags_in_spec(dict_tag)
        elif tag:
            tag_name = self.spec_tag[tag]["name"]

        urls = urls if urls else tuple()
        for i_url in urls:
            self._add_paths_in_spec(
                path=i_url,
                resource=resource,
                default_parameters=default_parameters,
                default_schema=default_schema,
                tag_name=tag_name,
                **kwargs,
            )

    @property
    def param_id(self) -> dict:
        return {
            "in": "path",
            "name": "id",
            "required": True,
            "type": "integer",
            "format": "int32",
        }

    @classmethod
    def _get_operations_for_all(cls, tag_name: str, default_parameters: list) -> Dict[str, Any]:
        """
        Creating base dict

        :param tag_name:
        :param default_parameters:
        :return:
        """
        return {
            "tags": [tag_name],
            "produces": ["application/json"],
            "parameters": default_parameters if default_parameters else [],
        }

    @classmethod
    def __get_parameters_for_include_models(cls, resource: Resource) -> dict:
        fields_names = [
            i_field_name
            for i_field_name, i_field in resource.schema._declared_fields.items()
            if isinstance(i_field, Relationship)
        ]
        models_for_include = ",".join(fields_names)
        example_models_for_include = "\n".join([f"`{f}`" for f in fields_names])
        return {
            "default": models_for_include,
            "name": "include",
            "in": "query",
            "format": "string",
            "required": False,
            "description": f"Related relationships to include.\nAvailable:\n{example_models_for_include}",
        }

    @classmethod
    def __get_parameters_for_sparse_fieldsets(cls, resource: Resource, description: str) -> dict:
        # Sparse Fieldsets
        return {
            "name": f"fields[{resource.schema.Meta.type_}]",
            "in": "query",
            "type": "array",
            "required": False,
            "description": description.format(resource.schema.Meta.type_),
            "items": {"type": "string", "enum": list(resource.schema._declared_fields.keys())},
        }

    def __get_parameters_for_declared_fields(self, resource, description) -> Generator[dict, None, None]:
        type_schemas = {resource.schema.Meta.type_}
        for i_field_name, i_field in resource.schema._declared_fields.items():
            if not (isinstance(i_field, Relationship) and i_field.schema.Meta.type_ not in type_schemas):
                continue
            schema_name = create_schema_name(schema=i_field.schema)
            new_parameter = {
                "name": f"fields[{i_field.schema.Meta.type_}]",
                "in": "query",
                "type": "array",
                "required": False,
                "description": description.format(i_field.schema.Meta.type_),
                "items": {
                    "type": "string",
                    "enum": list(self.spec.components._schemas[schema_name]["properties"].keys()),
                },
            }
            type_schemas.add(i_field.schema.Meta.type_)
            yield new_parameter

    @property
    def __list_filters_data(self) -> tuple:
        return (
            {
                "default": 1,
                "name": "page[number]",
                "in": "query",
                "format": "int64",
                "required": False,
                "description": "Page offset",
            },
            {
                "default": 10,
                "name": "page[size]",
                "in": "query",
                "format": "int64",
                "required": False,
                "description": "Max number of items",
            },
            {"name": "sort", "in": "query", "format": "string", "required": False, "description": "Sort",},
            {
                "name": "filter",
                "in": "query",
                "format": "string",
                "required": False,
                "description": "Filter (https://flask-combo-jsonapi.readthedocs.io/en/latest/filtering.html)",
            },
        )

    @classmethod
    def _update_parameter_for_field_spec(cls, new_param: dict, fld_sped: dict) -> None:
        """
        :param new_param:
        :param fld_sped:
        :return:
        """
        if "items" in fld_sped:
            new_items = {
                "type": fld_sped["items"].get("type"),
            }
            if "enum" in fld_sped["items"]:
                new_items["enum"] = fld_sped["items"]["enum"]
            new_param.update({"items": new_items})

    def __get_parameter_for_not_nested(self, field_name, field_spec) -> dict:
        new_parameter = {
            "name": f"filter[{field_name}]",
            "in": "query",
            "type": field_spec.get("type"),
            "required": False,
            "description": f"{field_name} attribute filter",
        }
        self._update_parameter_for_field_spec(new_parameter, field_spec)
        return new_parameter

    def __get_parameter_for_nested_with_filtering(self, field_name, field_jsonb_name, field_jsonb_spec):
        new_parameter = {
            "name": f"filter[{field_name}{SPLIT_REL}{field_jsonb_name}]",
            "in": "query",
            "type": field_jsonb_spec.get("type"),
            "required": False,
            "description": f"{field_name}{SPLIT_REL}{field_jsonb_name} attribute filter",
        }
        self._update_parameter_for_field_spec(new_parameter, field_jsonb_spec)
        return new_parameter

    def __get_parameters_for_nested_with_filtering(self, field, field_name) -> Generator[dict, None, None]:
        # Allow JSONB filtering
        field_schema_name = create_schema_name(schema=field.schema)
        component_schema = self.spec.components._schemas[field_schema_name]
        for i_field_jsonb_name, i_field_jsonb in field.schema._declared_fields.items():
            i_field_jsonb_spec = component_schema["properties"][i_field_jsonb_name]
            if i_field_jsonb_spec.get("type") == "object":
                # Пропускаем создание фильтров для dict. Просто не понятно как фильтровать по таким
                # полям
                continue
            new_parameter = self.__get_parameter_for_nested_with_filtering(
                field_name, i_field_jsonb_name, i_field_jsonb_spec,
            )
            yield new_parameter

    def __get_list_resource_fields_filters(self, resource) -> Generator[dict, None, None]:
        schema_name = create_schema_name(schema=resource.schema)
        for i_field_name, i_field in resource.schema._declared_fields.items():
            i_field_spec = self.spec.components._schemas[schema_name]["properties"][i_field_name]
            if not isinstance(i_field, fields.Nested):
                if i_field_spec.get("type") == "object":
                    # Skip filtering by dicts
                    continue
                yield self.__get_parameter_for_not_nested(i_field_name, i_field_spec)
            elif getattr(i_field.schema.Meta, "filtering", False):
                yield from self.__get_parameters_for_nested_with_filtering(i_field, i_field_name)

    def _get_operations_for_get(self, resource, tag_name, default_parameters):
        operations_get = self._get_operations_for_all(tag_name, default_parameters)
        operations_get["responses"] = {
            **status[HTTPStatus.OK],
            **status[HTTPStatus.NOT_FOUND],
        }

        if issubclass(resource, ResourceDetail):
            operations_get["parameters"].append(self.param_id)

        if resource.schema is None:
            return operations_get

        description = "List that refers to the name(s) of the fields to be returned `{}`"

        operations_get["parameters"].extend(
            (
                self.__get_parameters_for_include_models(resource),
                self.__get_parameters_for_sparse_fieldsets(resource, description),
            )
        )
        operations_get["parameters"].extend(self.__get_parameters_for_declared_fields(resource, description))

        if issubclass(resource, ResourceList):
            operations_get["parameters"].extend(self.__list_filters_data)
            operations_get["parameters"].extend(self.__get_list_resource_fields_filters(resource))

        return operations_get

    def _get_operations_for_post(self, schema: dict, tag_name: str, default_parameters: list) -> dict:
        operations = self._get_operations_for_all(tag_name, default_parameters)
        operations["responses"] = {
            "201": {"description": "Created"},
            "202": {"description": "Accepted"},
            "403": {"description": "This implementation does not accept client-generated IDs"},
            "404": {"description": "Not Found"},
            "409": {"description": "Conflict"},
        }
        operations["parameters"].append(
            {
                "name": "POST body",
                "in": "body",
                "schema": schema,
                "required": True,
                "description": f"{tag_name} attributes",
            }
        )
        return operations

    def _get_operations_for_patch(self, schema: dict, tag_name: str, default_parameters: list) -> dict:
        operations = self._get_operations_for_all(tag_name, default_parameters)
        operations["responses"] = {
            "200": {"description": "Success"},
            "201": {"description": "Created"},
            "204": {"description": "No Content"},
            "403": {"description": "Forbidden"},
            "404": {"description": "Not Found"},
            "409": {"description": "Conflict"},
        }
        operations["parameters"].append(self.param_id)
        operations["parameters"].append(
            {
                "name": "POST body",
                "in": "body",
                "schema": schema,
                "required": True,
                "description": f"{tag_name} attributes",
            }
        )
        return operations

    def _get_operations_for_delete(self, tag_name: str, default_parameters: list) -> dict:
        operations = self._get_operations_for_all(tag_name, default_parameters)
        operations["parameters"].append(self.param_id)
        operations["responses"] = {
            "200": {"description": "Success"},
            "202": {"description": "Accepted"},
            "204": {"description": "No Content"},
            "403": {"description": "Forbidden"},
            "404": {"description": "Not Found"},
        }
        return operations

    def _add_paths_in_spec(
        self,
        path: str = "",
        resource: Any = None,
        tag_name: str = "",
        default_parameters: List = None,
        default_schema: Schema = None,
        **kwargs,
    ) -> None:
        operations = {}
        methods: Set[str] = {i_method.lower() for i_method in resource.methods}

        attributes = {}
        if resource.schema:
            attributes = {"$ref": f"#/definitions/{create_schema_name(resource.schema)}"}
        schema = (
            default_schema
            if default_schema
            else {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "type": {
                                "type": "string",
                            },
                            "id": {
                                "type": "string",
                            },
                            "attributes": attributes,
                            "relationships": {
                                "type": "object",
                            },
                        },
                        "required": [
                            "type",
                        ],
                    },
                },
            }
        )

        if "get" in methods:
            operations["get"] = self._get_operations_for_get(resource, tag_name, default_parameters)
        if "post" in methods:
            operations["post"] = self._get_operations_for_post(schema, tag_name, default_parameters)
        if "patch" in methods:
            operations["patch"] = self._get_operations_for_patch(schema, tag_name, default_parameters)
        if "delete" in methods:
            operations["delete"] = self._get_operations_for_delete(tag_name, default_parameters)
        rule = None
        for i_rule in self.app.url_map._rules:
            if i_rule.rule == path:
                rule = i_rule
                break
        if APISPEC_VERSION_MAJOR < 1:
            self.spec.add_path(path=path, operations=operations, rule=rule, resource=resource, **kwargs)
        else:
            self.spec.path(path=path, operations=operations, rule=rule, resource=resource, **kwargs)

    def _add_definitions_in_spec(self, schema) -> None:
        """
        Add schema in spec
        :param schema: schema marshmallow
        :return:
        """
        name_schema = create_schema_name(schema)
        if name_schema not in self.spec_schemas and name_schema not in self.spec.components._schemas:
            self.spec_schemas[name_schema] = schema
            if APISPEC_VERSION_MAJOR < 1:
                self.spec.definition(name_schema, schema=schema)
            else:
                self.spec.components.schema(name_schema, schema=schema)

    def _add_tags_in_spec(self, tag: Dict[str, str]) -> None:
        """
        Add tags in spec
        :param tag: {'name': '<name tag>', 'description': '<tag description>', 'add_in_spec': <added tag in spec?>}
        :return:
        """
        if tag.get("add_in_spec", True) is False:
            self.spec_tag[tag["name"]]["add_in_spec"] = True
            tag_in_spec = {"name": tag["name"], "description": tag["description"]}
            if APISPEC_VERSION_MAJOR < 1:
                self.spec.add_tag(tag_in_spec)
            else:
                self.spec.tag(tag_in_spec)
Exemplo n.º 17
0
from apispec import APISpec
from flask import json

from app import create_app

# Create spec
spec = APISpec(title='Restaurant API',
               version='0.1.0',
               info=dict(description='You know, for devs'),
               plugins=['apispec.ext.flask', 'apispec.ext.marshmallow'])

# Reference your schemas definitions
from schemas.restaurant import RestaurantSchema

spec.definition('Restaurant', schema=RestaurantSchema)
# ...

# Now, reference your routes.
from views.restaurants import get_restaurants, get_restaurant

# We need a working context for apispec introspection.
app = create_app()

with app.test_request_context():
    spec.add_path(view=get_restaurants)
    spec.add_path(view=get_restaurant)
    # ...

# We're good to go! Save this to a file for now.
with open('swagger.json', 'w') as f:
    json.dump(spec.to_dict(), f)
Exemplo n.º 18
0
        result = UserSchema().dump(user).data
        response = jsonify(result)
        return response


class ProtectedResource(MethodView):
    @login_required
    def get(self):
        return jsonify({'logged_user': g.logged_user})


@app.route('/swagger.json')
def swagger():
    return jsonify(spec.to_dict())


spec.definition('User', schema=UserSchema)


def add_url_rule_and_spec_path(url, cls):
    method_view = cls.as_view(cls.__name__.lower())
    app.add_url_rule(url, view_func=method_view)

    with app.app_context():
        spec.add_path(view=method_view)


add_url_rule_and_spec_path('/user', UserListResource)
add_url_rule_and_spec_path('/user/<int:id>', UserResource)
add_url_rule_and_spec_path('/protected', ProtectedResource)
Exemplo n.º 19
0
@app.route('/random')
def random_pet():
    """A cute furry animal endpoint.
    ---
    get:
        description: Get a random pet
        responses:
            200:
                description: A pet to be returned
                schema: PetSchema
    post:
        description: Get a random pet
        responses:
            200:
                description: A pet to be returned
                schema: PetSchema
    """
    cat = dict(id=1, name='cat')
    pet = dict(name='god', category=[cat])
    # pet = get_random_pet()
    return jsonify(PetSchema().dump(pet).data)


ctx = app.test_request_context()
ctx.push()

# Register entities and paths
spec.definition('Category', schema=CategorySchema)
spec.definition('Pet', schema=PetSchema)
spec.add_path(view=random_pet)
Exemplo n.º 20
0
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin

from lspcheminf import app
import lspcheminf.schemas as schemas

spec = APISpec(
    title="TAS Chemoinformatics tools",
    version="0.4.2",
    openapi_version="3.0.2",
    plugins=[FlaskPlugin(), MarshmallowPlugin()],
)

for i in dir(schemas):
    c = getattr(schemas, i)
    try:
        if issubclass(c, marshmallow.Schema):
            spec.definition(i, c)
    except Exception:
        pass

with app.test_request_context():
    for p in app.view_functions.values():
        spec.path(view=p)

if __name__ == "__main__":
    with open("./lsp_cheminf_server/lspcheminf/static/apidoc.json", "w") as f:
        json.dump(spec.to_dict(), f)
    with open("./docs/api/apidoc.json", "w") as f:
        json.dump(spec.to_dict(), f)
Exemplo n.º 21
0
def test_openapi_tools_validate_v3():
    ma_plugin = MarshmallowPlugin()
    spec = APISpec(
        title='Pets',
        version='0.1',
        plugins=(ma_plugin, ),
        openapi_version='3.0.0',
    )
    #openapi = ma_plugin.openapi

    spec.definition('Category', schema=CategorySchema)
    spec.definition('Pet', schema=PetSchemaV3)

    spec.add_path(
        view=None,
        path='/category/{category_id}',
        operations={
            'get': {
                'parameters': [
                    {
                        'name': 'q',
                        'in': 'query',
                        'schema': {
                            'type': 'string'
                        },
                    },
                    {
                        'name': 'category_id',
                        'in': 'path',
                        'required': True,
                        'schema': {
                            'type': 'string'
                        },
                    },
                ],  # + openapi.schema2parameters(PageSchema, default_in='query'),
                'responses': {
                    200: {
                        'description': 'success',
                        'content': {
                            'application/json': {
                                'schema': PetSchemaV3,
                            },
                        },
                    },
                },
            },
            'post': {
                'parameters': ([
                    {
                        'name': 'category_id',
                        'in': 'path',
                        'required': True,
                        'schema': {
                            'type': 'string'
                        },
                    },
                ]),
                'requestBody': {
                    'content': {
                        'application/json': {
                            'schema': CategorySchema,
                        },
                    },
                },
                'responses': {
                    201: {
                        'description': 'created',
                        'content': {
                            'application/json': {
                                'schema': PetSchemaV3,
                            },
                        },
                    },
                },
            },
        },
    )
    try:
        utils.validate_spec(spec)
    except exceptions.OpenAPIError as error:
        pytest.fail(str(error))
Exemplo n.º 22
0
               version='1.0.0',
               info=dict(description='一个简单的gist demo api'),
               basePath="/v1.0",
               tags=[{
                   "name": "user",
                   "description": "获取用户信息"
               }],
               plugins=["apispec.ext.tornado", "apispec.ext.marshmallow"])

spec.definition("http400",
                properties={
                    "status_code": {
                        "type": "integer",
                        "default": 400
                    },
                    "message": {
                        "type": "string"
                    },
                    "errors": {
                        "type": "array",
                        "$ref": "#definitions/error"
                    },
                })

spec.definition("error",
                properties={
                    "field": {
                        "type": "array",
                        "description": "出错列"
                    },
                })
spec = APISpec(
    title='mypackage',
    version='0.0.1',
    info=dict(
        description='${description}'
    ),
    plugins=['apispec.ext.marshmallow']
)


class PersonSchema(Schema):
  age = fields.Int()
  name = fields.String()
    
spec.definition('Person', schema=PersonSchema)


spec.add_path(
    path='/randomPerson',
    operations=dict(
        get=dict(
            responses={
                '${op.responses().success().code()}': {
                    'schema': {'$ref': '#/definitions/Person'}
                }
            }
        )
    )
)
Exemplo n.º 24
0
               })

app = create_app()
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
cli.register(app)

from app.api.users import get_user, get_users  # noqa: F401
from app.api.users import create_user, update_user  # noqa: F401
from app.api.users import get_followers, get_followed  # noqa: F401
from app.api.tokens import get_token  # noqa: F401
from app.api.schemas.users import LinkSchema, UserListApiSchema  # noqa: F401
from app.api.schemas.users import UserApiSchema, MetaSchema  # noqa: F401
from app.api.schemas.users import UserPostApiSchema  # noqa: F401
from app.api.schemas.users import UserPutApiSchema  # noqa: F401
from app.api.schemas.users import TokenApiSchema  # noqa: F401
spec.definition('User', schema=UserApiSchema)
spec.definition('Users', schema=UserListApiSchema)
spec.definition('Links', schema=LinkSchema)
spec.definition('Meta', schema=MetaSchema)
spec.definition('CreateUser', schema=UserPostApiSchema)
spec.definition('UpdateUser', schema=UserPutApiSchema)
spec.definition('Token', schema=TokenApiSchema)

with app.test_request_context():
    spec.add_path(view=get_user)
    spec.add_path(view=get_users)
    spec.add_path(view=get_followers)
    spec.add_path(view=get_followed)
    spec.add_path(view=create_user)
    spec.add_path(view=update_user)
    spec.add_path(view=get_token)
Exemplo n.º 25
0
Arquivo: app.py Projeto: saalaa/adoc
    data = {'status': 'ok'}

    return jsonify(StatusResponse().dump(data).data)


if __name__ == '__main__':
    import json

    from apispec import APISpec
    from apispec.ext.flask import FlaskPlugin
    from apispec.ext.marshmallow import MarshmallowPlugin

    spec = APISpec(title='Flask project',
                   version='v1',
                   openapi_version='2.0',
                   host='example.com',
                   basePath='/api',
                   schemes=['https'],
                   consumes=['application/json'],
                   produces=['application/json'],
                   plugins=[FlaskPlugin(), MarshmallowPlugin()])

    spec.definition('StatusResponse', schema=StatusResponse)

    with app.test_request_context():
        spec.add_path(view=api_status)

    spec = spec.to_dict()

    print(json.dumps(spec, indent=2))
Exemplo n.º 26
0
"""baseAPI.py - Creates a shared specification object for Swagger / OpenAPI 2.0 specification"""
from apispec import APISpec

# Create the API APISpec
BASE_SPEC = APISpec(
    title='Wittle South LLC Base Project - API Specification',
    version='1.0.0',
    info=dict(description='API Specification for base project APIs'),
    plugins=['apispec.ext.marshmallow', 'src.APIs.apispecFlaskRestful'])

BASE_SPEC.definition('SuccessResponse',
                     properties={
                         'success': {
                             'type': 'boolean'
                         },
                         'msg': {
                             'type': 'string'
                         },
                         'id': {
                             'type': 'integer'
                         }
                     })

BASE_SPEC.definition('ReferenceLinks',
                     properties={
                         'relationship': {
                             'type': 'string'
                         },
                         'uri': {
                             'type': 'string'
                         }
                     })
Exemplo n.º 27
0
def _load_swagger(url_specs, title=None):
    global api_spec
    api_spec = APISpec(
        title=title,
        version="1.0",
        plugins=("apispec.ext.marshmallow", "apispec.ext.tornado"),
        securityDefinitions={
            "Bearer": {
                "type": "apiKey",
                "name": "Authorization",
                "in": "header"
            },
        },
        security=[{
            "Bearer": []
        }],
    )

    # Schemas from Marshmallow
    api_spec.definition("Parameter", schema=ParameterSchema)
    api_spec.definition("Command", schema=CommandSchema)
    api_spec.definition("Instance", schema=InstanceSchema)
    api_spec.definition("Request", schema=RequestSchema)
    api_spec.definition("System", schema=SystemSchema)
    api_spec.definition("LoggingConfig", schema=LoggingConfigSchema)
    api_spec.definition("Event", schema=EventSchema)
    api_spec.definition("User", schema=UserSchema)
    api_spec.definition("UserCreate", schema=UserCreateSchema)
    api_spec.definition("UserList", schema=UserListSchema)
    api_spec.definition("UserPatch", schema=UserPatchSchema)
    api_spec.definition("UserPasswordChange", schema=UserPasswordChangeSchema)
    api_spec.definition("Role", schema=LegacyRoleSchema)
    api_spec.definition("RoleList", schema=RoleListSchema)
    api_spec.definition("Queue", schema=QueueSchema)
    api_spec.definition("Operation", schema=OperationSchema)
    api_spec.definition("FileStatus", schema=FileStatusSchema)
    api_spec.definition("TokenInput", schema=TokenInputSchema)
    api_spec.definition("TokenRefreshInput", schema=TokenRefreshInputSchema)
    api_spec.definition("TokenResponse", schema=TokenResponseSchema)

    api_spec.definition("Garden", schema=GardenSchema)
    api_spec.definition("Runner", schema=RunnerSchema)

    api_spec.definition("_patch", schema=PatchSchema)
    api_spec.definition(
        "Patch",
        properties={
            "operations": {
                "type": "array",
                "items": {
                    "$ref": "#/definitions/_patch"
                }
            }
        },
    )
    api_spec.definition("DateTrigger", schema=DateTriggerSchema)
    api_spec.definition("CronTrigger", schema=CronTriggerSchema)
    api_spec.definition("IntervalTrigger", schema=IntervalTriggerSchema)
    api_spec.definition("Job", schema=JobSchema)
    trigger_properties = {
        "allOf": [
            {
                "$ref": "#/definitions/CronTrigger"
            },
            {
                "$ref": "#/definitions/DateTrigger"
            },
            {
                "$ref": "#/definitions/IntervalTrigger"
            },
        ]
    }
    api_spec._definitions["Job"]["properties"][
        "trigger"] = trigger_properties  # noqa

    api_spec.definition("JobExport", schema=JobExportInputSchema)
    api_spec.definition("JobImport", schema=JobExportSchema)
    api_spec._definitions["JobImport"]["properties"][  # noqa
        "trigger"] = trigger_properties

    error = {"message": {"type": "string"}}
    api_spec.definition("400Error",
                        properties=error,
                        description="Parameter validation error")
    api_spec.definition("401Error",
                        properties=error,
                        description="Authorization required")
    api_spec.definition("403Error",
                        properties=error,
                        description="Access denied")
    api_spec.definition("404Error",
                        properties=error,
                        description="Resource does not exist")
    api_spec.definition("409Error",
                        properties=error,
                        description="Resource already exists")
    api_spec.definition("50xError",
                        properties=error,
                        description="Server exception")

    # Finally, add documentation for all our published paths
    for url_spec in url_specs:
        api_spec.add_path(urlspec=url_spec)
Exemplo n.º 28
0
    def test_resolve_schema_dict_auto_reference_custom_schema_resolver(
            self, schema):
        def resolver(schema):
            if getattr(schema, '_schema_name', None):
                return schema._schema_name
            else:
                return schema.__name__

        def class_resolver_handle_exclude(spec, schema):
            if isinstance(schema, type):
                return schema
            else:
                if getattr(schema, 'exclude', ()):
                    exclude = set(getattr(schema, 'exclude', ()))
                    str_exclude = ''
                    for elem in exclude:
                        str_exclude += '_{}'.format(elem)
                    cls_schema = type(schema)

                    class NewSchema(cls_schema):
                        pass

                    NewSchema.opts.exclude = exclude
                    NewSchema.__name__ = cls_schema.__name__
                    NewSchema._schema_name = '{}-{}'.format(
                        cls_schema.__name__,
                        str_exclude,
                    )
                    return NewSchema
                else:
                    return type(schema)

        spec = APISpec(
            title='Test auto-reference',
            version='2.0',
            description='Test auto-reference',
            plugins=('apispec.ext.marshmallow', ),
            schema_name_resolver=resolver,
            schema_class_resolver=class_resolver_handle_exclude,
            auto_referencing=True,
        )
        assert {} == spec._definitions

        spec.definition('analysis', schema=schema)
        spec.add_path('/test',
                      operations={
                          'get': {
                              'responses': {
                                  '200': {
                                      'schema': {
                                          '$ref': '#/definitions/analysis'
                                      }
                                  }
                              }
                          }
                      })
        assert 3 == len(spec._definitions)

        assert 'analysis' in spec._definitions
        assert 'SampleSchema' in spec._definitions
        assert 'RunSchema-_sample' in spec._definitions
Exemplo n.º 29
0
    def api_spec(self):
        """
        OpenApi 2.0 spec
        ---
        get:
          tags:
          - "OpenApi 2.0 spec"
          summary: "Return openapi spec
          purposes"
          description: ""
          operationId: "api_spec"
          consumes:
          - "application/json"
          produces:
          - "application/json"
          parameters:
          responses:
            200:
              description: "Success"
        """
        spec = APISpec(title="Channelstream API",
                       version="0.7.0",
                       plugins=(MarshmallowPlugin(), ))
        spec.definition("ConnectBody", schema=schemas.ConnectBodySchema)
        spec.definition("SubscribeBody", schema=schemas.SubscribeBodySchema)
        spec.definition("UnsubscribeBody",
                        schema=schemas.UnsubscribeBodySchema)
        spec.definition("UserStateBody", schema=schemas.UserStateBodySchema)
        spec.definition("MessagesBody",
                        schema=schemas.MessageBodySchema(many=True))
        spec.definition("MessageBody", schema=schemas.MessageBodySchema())
        spec.definition("MessageEditBody",
                        schema=schemas.MessageEditBodySchema(many=True))
        spec.definition("MessagesDeleteBody",
                        schema=schemas.MessagesDeleteBodySchema(many=True))
        spec.definition("DisconnectBody", schema=schemas.DisconnectBodySchema)
        spec.definition("ChannelConfigBody",
                        schema=schemas.ChannelConfigSchema)
        spec.definition("ChannelInfoBody",
                        schema=schemas.ChannelInfoBodySchema)

        # legacy api
        add_pyramid_paths(spec, "legacy_connect", request=self.request)
        add_pyramid_paths(spec, "legacy_subscribe", request=self.request)
        add_pyramid_paths(spec, "legacy_unsubscribe", request=self.request)
        add_pyramid_paths(spec, "legacy_user_state", request=self.request)
        add_pyramid_paths(spec, "legacy_message", request=self.request)
        add_pyramid_paths(spec, "legacy_channel_config", request=self.request)
        add_pyramid_paths(spec, "legacy_info", request=self.request)

        add_pyramid_paths(spec, "api_listen", request=self.request)
        add_pyramid_paths(spec, "api_listen_ws", request=self.request)
        add_pyramid_paths(spec, "api_disconnect", request=self.request)

        # v1 api
        # do not expose this yet
        # add_pyramid_paths(spec, "api_v1_messages", request=self.request)

        add_pyramid_paths(spec, "admin_json", request=self.request)
        spec_dict = spec.to_dict()
        spec_dict["securityDefinitions"] = {
            "APIKeyHeader": {
                "type": "apiKey",
                "name": "X-Channelstream-Secret",
                "in": "header",
            }
        }
        return spec_dict
Exemplo n.º 30
0
def _load_swagger(url_specs, title=None):

    global api_spec
    api_spec = APISpec(title=title, version='1.0',
                       plugins=('apispec.ext.marshmallow', 'apispec.ext.tornado'))

    # Schemas from Marshmallow
    api_spec.definition('Parameter', schema=ParameterSchema)
    api_spec.definition('Command', schema=CommandSchema)
    api_spec.definition('Instance', schema=InstanceSchema)
    api_spec.definition('Request', schema=RequestSchema)
    api_spec.definition('System', schema=SystemSchema)
    api_spec.definition('LoggingConfig', schema=LoggingConfigSchema)
    api_spec.definition('Event', schema=EventSchema)
    api_spec.definition('User', schema=PrincipalSchema)
    api_spec.definition('Role', schema=RoleSchema)
    api_spec.definition('Queue', schema=QueueSchema)
    api_spec.definition('RefreshToken', schema=RefreshTokenSchema)
    api_spec.definition('_patch', schema=PatchSchema)
    api_spec.definition('Patch', properties={"operations": {
        "type": "array", "items": {"$ref": "#/definitions/_patch"}}
    })
    api_spec.definition('DateTrigger', schema=DateTriggerSchema)
    api_spec.definition('CronTrigger', schema=CronTriggerSchema)
    api_spec.definition('IntervalTrigger', schema=IntervalTriggerSchema)
    api_spec.definition('Job', schema=JobSchema)
    trigger_properties = {
        'allOf': [
            {'$ref': '#/definitions/CronTrigger'},
            {'$ref': '#/definitions/DateTrigger'},
            {'$ref': '#/definitions/IntervalTrigger'},
        ],
    }
    api_spec._definitions['Job']['properties']['trigger'] = trigger_properties

    error = {'message': {'type': 'string'}}
    api_spec.definition('400Error', properties=error, description='Parameter validation error')
    api_spec.definition('404Error', properties=error, description='Resource does not exist')
    api_spec.definition('409Error', properties=error, description='Resource already exists')
    api_spec.definition('50xError', properties=error, description='Server exception')

    # Finally, add documentation for all our published paths
    for url_spec in url_specs:
        api_spec.add_path(urlspec=url_spec)
class Inspector(object):  # TODO: type everything
    def __init__(self, declaration) -> None:
        self.declaration = declaration
        self.spec = APISpec(  # TODO: user must provide this information
            title='Gisty',
            version='1.0.0',
            openapi_version='2.0',
            info={
                'description': 'A minimal gist API',
            },
        )

    def _operation_internals(self, operation):
        payload_name = operation.response_payload
        if issubclass(operation.response_payload, list):
            payload_name = payload_name.__args__[0]  # TODO: handle list
        else:
            payload_name = operation.response_payload

        # TODO: create separate handlers for list and regular items
        payload_name = payload_name.__qualname__
        if payload_name not in self.spec._definitions:
            self.spec.definition(payload_name,
                                 properties={
                                     'id': {
                                         'type': 'integer',
                                         'format': 'int64'
                                     },
                                     'name': {
                                         'type': 'string'
                                     },
                                 })

        return {  # TODO: provide other information, not just `responses`
            'responses': {
                200: {  # TODO: status code
                    'description': inspect.getdoc(operation),
                    'schema': {  # TODO: handle list
                        '$ref': '#/definitions/' + payload_name,
                    },
                },
            },
        }

    def _operations_from_endpoint(self, endpoint):
        # TODO: sync names: operation, methods, endpoints, etc
        methods = [method.name for method in fields(endpoint)]
        existing_operations = {}
        for method_name in methods:
            method_declaration = getattr(endpoint, method_name, None)
            if method_declaration is not None:
                existing_operations[method_name] = self._operation_internals(
                    method_declaration, )
        return existing_operations

    def generate_schema(self) -> dict:
        endpoints = self.declaration.endpoints
        for url_prefix, endpoint_declaration in endpoints.items():
            endpoint, name = endpoint_declaration
            self.spec.add_path(
                path='/' + url_prefix,  # TODO: normalize url
                operations=self._operations_from_endpoint(endpoint),
            )

        validate_spec(self.spec)
        return self.spec.to_dict()
Exemplo n.º 32
0
# encoding: utf-8
import yaml
from apispec import APISpec

spec = APISpec(title='Gisty',
               version='1.0.0',
               info=dict(description='一个简单的gist demo api'))

spec.definition('Gist',
                properties={
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "name": {
                        "type": "string"
                    }
                })

spec.add_path(
    path="/gist/{gist_id}",
    operations=dict(get=dict(
        responses={"200": {
            "schema": {
                "$ref": "#/definations/Gist"
            }
        }})))

print(spec.to_dict())
docs = yaml.dump(spec.to_dict())
print(docs)
Exemplo n.º 33
0
def _load_swagger(url_specs, title=None):
    global api_spec
    api_spec = APISpec(
        title=title,
        version="1.0",
        plugins=("apispec.ext.marshmallow", "apispec.ext.tornado"),
    )

    # Schemas from Marshmallow
    api_spec.definition("Parameter", schema=ParameterSchema)
    api_spec.definition("Command", schema=CommandSchema)
    api_spec.definition("Instance", schema=InstanceSchema)
    api_spec.definition("Request", schema=RequestSchema)
    api_spec.definition("System", schema=SystemSchema)
    api_spec.definition("LoggingConfig", schema=LoggingConfigSchema)
    api_spec.definition("Event", schema=EventSchema)
    api_spec.definition("User", schema=PrincipalSchema)
    api_spec.definition("Role", schema=RoleSchema)
    api_spec.definition("Queue", schema=QueueSchema)
    api_spec.definition("Operation", schema=OperationSchema)
    api_spec.definition("FileStatus", schema=FileStatusSchema)

    api_spec.definition("RefreshToken", schema=RefreshTokenSchema)

    api_spec.definition("Garden", schema=GardenSchema)
    api_spec.definition("Runner", schema=RunnerSchema)

    api_spec.definition("_patch", schema=PatchSchema)
    api_spec.definition(
        "Patch",
        properties={
            "operations": {"type": "array", "items": {"$ref": "#/definitions/_patch"}}
        },
    )
    api_spec.definition("DateTrigger", schema=DateTriggerSchema)
    api_spec.definition("CronTrigger", schema=CronTriggerSchema)
    api_spec.definition("IntervalTrigger", schema=IntervalTriggerSchema)
    api_spec.definition("Job", schema=JobSchema)
    trigger_properties = {
        "allOf": [
            {"$ref": "#/definitions/CronTrigger"},
            {"$ref": "#/definitions/DateTrigger"},
            {"$ref": "#/definitions/IntervalTrigger"},
        ]
    }
    api_spec._definitions["Job"]["properties"]["trigger"] = trigger_properties

    error = {"message": {"type": "string"}}
    api_spec.definition(
        "400Error", properties=error, description="Parameter validation error"
    )
    api_spec.definition(
        "404Error", properties=error, description="Resource does not exist"
    )
    api_spec.definition(
        "409Error", properties=error, description="Resource already exists"
    )
    api_spec.definition("50xError", properties=error, description="Server exception")

    # Finally, add documentation for all our published paths
    for url_spec in url_specs:
        api_spec.add_path(urlspec=url_spec)
Exemplo n.º 34
0
def test_swagger_tools_validate():
    spec = APISpec(
        title='Pets',
        version='0.1',
        plugins=['apispec.ext.marshmallow'],
    )

    spec.definition('Category', schema=CategorySchema)
    spec.definition('Pet',
                    schema=PetSchema,
                    extra_fields={'discriminator': 'name'})

    spec.add_path(
        view=None,
        path='/category/{category_id}',
        operations={
            'get': {
                'parameters': [
                    {
                        'name': 'q',
                        'in': 'query',
                        'type': 'string'
                    },
                    {
                        'name': 'category_id',
                        'in': 'path',
                        'required': True,
                        'type': 'string'
                    },
                    field2parameter(
                        field=fields.List(
                            fields.Str(),
                            validate=validate.OneOf(['freddie', 'roger']),
                            location='querystring',
                        ),
                        name='body',
                        use_refs=False,
                    ),
                ] + swagger.schema2parameters(PageSchema, default_in='query'),
                'responses': {
                    200: {
                        'schema': PetSchema,
                        'description': 'A pet',
                    },
                },
            },
            'post': {
                'parameters': ([{
                    'name': 'category_id',
                    'in': 'path',
                    'required': True,
                    'type': 'string'
                }] + swagger.schema2parameters(
                    CategorySchema, spec=spec, default_in='body')),
                'responses': {
                    201: {
                        'schema': PetSchema,
                        'description': 'A pet',
                    },
                },
            }
        },
    )
    try:
        utils.validate_swagger(spec)
    except exceptions.SwaggerError as error:
        pytest.fail(str(error))
Exemplo n.º 35
0
class Teal(Flask):
    """
    An opinionated REST and JSON first server built on Flask using
    MongoDB and Marshmallow.
    """
    test_client_class = Client
    request_class = Request
    json_encoder = TealJSONEncoder
    cli_context_settings = {'help_option_names': ('-h', '--help')}
    test_cli_runner_class = TealCliRunner

    def __init__(self,
                 config: ConfigClass,
                 db: SQLAlchemy,
                 schema: str = None,
                 import_name=__name__.split('.')[0],
                 static_url_path=None,
                 static_folder='static',
                 static_host=None,
                 host_matching=False,
                 subdomain_matching=False,
                 template_folder='templates',
                 instance_path=None,
                 instance_relative_config=False,
                 root_path=None,
                 use_init_db=True,
                 Auth: Type[Auth] = Auth):
        """

        :param config:
        :param db:
        :param schema: A string describing the main PostgreSQL's schema.
                      ``None`` disables this functionality.
                      If you use a factory of apps (for example by using
                      :func:`teal.teal.prefixed_database_factory`) and then set this
                      value differently per each app (as each app has a separate config)
                      you effectively create a `multi-tenant app <https://
                      news.ycombinator.com/item?id=4268792>`_.
                      Your models by default will be created in this ``SCHEMA``,
                      unless you set something like::

                          class User(db.Model):
                              __table_args__ = {'schema': 'users'}

                      In which case this will be created in the ``users`` schema.
                      Schemas are interesting over having multiple databases (i.e. using
                      flask-sqlalchemy's data binding) because you can have relationships
                      between them.

                      Note that this only works with PostgreSQL.
        :param import_name:
        :param static_url_path:
        :param static_folder:
        :param static_host:
        :param host_matching:
        :param subdomain_matching:
        :param template_folder:
        :param instance_path:
        :param instance_relative_config:
        :param root_path:
        :param Auth:
        """
        self.schema = schema
        ensure_utf8(self.__class__.__name__)
        super().__init__(import_name, static_url_path, static_folder,
                         static_host, host_matching, subdomain_matching,
                         template_folder, instance_path,
                         instance_relative_config, root_path)
        self.config.from_object(config)
        flask_cors.CORS(self)
        # Load databases
        self.auth = Auth()
        self.url_map.converters[Converters.lower.name] = LowerStrConverter
        self.load_resources()
        self.register_error_handler(HTTPException, self._handle_standard_error)
        self.register_error_handler(ValidationError,
                                    self._handle_validation_error)
        self.db = db
        db.init_app(self)
        if use_init_db:
            self.cli.command('init-db',
                             context_settings=self.cli_context_settings)(
                                 self.init_db)
        self.spec = None  # type: APISpec
        self.apidocs()

    # noinspection PyAttributeOutsideInit
    def load_resources(self):
        self.resources = {}  # type: Dict[str, Resource]
        """
        The resources definitions loaded on this App, referenced by their
        type name.
        """
        self.tree = {}  # type: Dict[str, Node]
        """
        A tree representing the hierarchy of the instances of 
        ResourceDefinitions. ResourceDefinitions use these nodes to
        traverse their hierarchy.
         
        Do not use the normal python class hierarchy as it is global,
        thus unreliable if you run different apps with different
        schemas (for example, an extension that is only added on the
        third app adds a new type of user).
        """
        for ResourceDef in self.config['RESOURCE_DEFINITIONS']:
            resource_def = ResourceDef(self)  # type: Resource
            self.register_blueprint(resource_def)

            if resource_def.cli_commands:

                @self.cli.group(resource_def.cli_name,
                                context_settings=self.cli_context_settings,
                                short_help='{} management.'.format(
                                    resource_def.type))
                def dummy_group():
                    pass

            for cli_command, *args in resource_def.cli_commands:  # Register CLI commands
                # todo cli commands with multiple arguments end-up reversed
                # when teal has been executed multiple times (ex. testing)
                # see _param_memo func in click package
                dummy_group.command(*args)(cli_command)

            # todo should we use resource_def.name instead of type?
            # are we going to have collisions? (2 resource_def -> 1 schema)
            self.resources[resource_def.type] = resource_def
            self.tree[resource_def.type] = Node(resource_def.type)
        # Link tree nodes between them
        for _type, node in self.tree.items():
            resource_def = self.resources[_type]
            _, Parent, *superclasses = inspect.getmro(resource_def.__class__)
            if Parent is not Resource:
                node.parent = self.tree[Parent.type]

    @staticmethod
    def _handle_standard_error(e: HTTPException):
        """
        Handles HTTPExceptions by transforming them to JSON.
        """
        try:
            response = jsonify(e)
            response.status_code = e.code
        except (AttributeError, TypeError) as e:
            code = getattr(e, 'code', 500)
            response = jsonify({
                'message': str(e),
                'code': code,
                'type': e.__class__.__name__
            })
            response.status_code = code
        return response

    @staticmethod
    def _handle_validation_error(e: ValidationError):
        data = {
            'message': e.messages,
            'code': UnprocessableEntity.code,
            'type': e.__class__.__name__
        }
        response = jsonify(data)
        response.status_code = UnprocessableEntity.code
        return response

    @option(
        '--erase/--no-erase',
        default=False,
        help='Delete all contents from the database (including common schemas)?'
    )
    @option(
        '--exclude-schema',
        default=None,
        help='Schema to exclude creation (and deletion if --erase is set). '
        'Required the SchemaSQLAlchemy.')
    def init_db(self, erase: bool = False, exclude_schema=None):
        """
        Initializes a database from scratch,
        creating tables and needed resources.

        Note that this does not create the database per se.

        If executing this directly, remember to use an app_context.

        Resources can hook functions that will be called when this
        method executes, by subclassing :meth:`teal.resource.
        Resource.load_resource`.
        """
        assert _app_ctx_stack.top, 'Use an app context.'
        print('Initializing database...'.ljust(30), end='')
        with click_spinner.spinner():
            if erase:
                if exclude_schema:  # Using then a schema teal sqlalchemy
                    assert isinstance(self.db, SchemaSQLAlchemy)
                    self.db.drop_schema()
                else:  # using regular flask sqlalchemy
                    self.db.drop_all()
            self._init_db(exclude_schema)
            self._init_resources()
            self.db.session.commit()
        print('done.')

    def _init_db(self, exclude_schema=None) -> bool:
        """Where the database is initialized. You can override this.

        :return: A flag stating if the database has been created (can
        be False in case check is True and the schema already
        exists).
        """
        if exclude_schema:  # Using then a schema teal sqlalchemy
            assert isinstance(self.db, SchemaSQLAlchemy)
            self.db.create_all(exclude_schema=exclude_schema)
        else:  # using regular flask sqlalchemy
            self.db.create_all()
        return True

    def _init_resources(self, **kw):
        for resource in self.resources.values():
            resource.init_db(self.db, **kw)

    def apidocs(self):
        """Apidocs configuration and generation."""
        self.spec = APISpec(plugins=(
            'apispec.ext.flask',
            'apispec.ext.marshmallow',
        ),
                            **self.config.get_namespace('API_DOC_CONFIG_'))
        for name, resource in self.resources.items():
            if resource.SCHEMA:
                self.spec.definition(
                    name,
                    schema=resource.SCHEMA,
                    extra_fields=self.config.get_namespace('API_DOC_CLASS_'))
        self.add_url_rule('/apidocs', view_func=self.apidocs_endpoint)

    def apidocs_endpoint(self):
        """An endpoint that prints a JSON OpenApi 2.0 specification."""
        if not getattr(self, '_apidocs', None):
            # We are forced to to this under a request context
            for path, view_func in self.view_functions.items():
                if path != 'static':
                    self.spec.add_path(view=view_func)
            self._apidocs = self.spec.to_dict()
        return jsonify(self._apidocs)