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)
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()
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
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)
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
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
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
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()
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)
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
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)
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()
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)
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' } } } }
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
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)
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)
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)
@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)
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)
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))
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'} } } ) ) )
}) 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)
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))
"""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' } })
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)
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
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
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()
# 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)
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)
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))
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)