def get_operations(view, operations, autodoc=True): if operations is not None: return operations operations = {} # views can be class based if view.get("attr"): global_meta = load_operations_from_docstring(view["callable"].__doc__) if global_meta: operations.update(global_meta) f_view = getattr(view["callable"], view["attr"]) # or just function callables else: f_view = view.get("callable") methods = view.get("request_methods") view_operations = load_operations_from_docstring(f_view.__doc__) if not view_operations: view_operations = {} if is_string(methods): methods = [methods] if not methods: methods = ALL_METHODS[:] operation = load_yaml_from_docstring(f_view.__doc__) if operation: for method in methods: view_operations[method.lower()] = operation elif autodoc: for method in methods: view_operations.setdefault(method.lower(), {"responses": {}}) operations.update(view_operations) return operations
def get_operations(spec, uri_pattern, view, operations, show_head, show_options, cornice_service, autodoc=True): if operations is not None: return operations operations = {} # views can be class based if view.get("attr"): global_meta = load_operations_from_docstring(view["callable"].__doc__) if global_meta: operations.update(global_meta) f_view = getattr(view["callable"], view["attr"]) # or just function callables else: f_view = view.get("callable") methods = view.get("request_methods") view_operations = load_operations_from_docstring(f_view.__doc__) if not view_operations: view_operations = {} if is_string(methods): methods = [methods] if not methods: methods = ALL_METHODS[:] if 'HEAD' in methods and not show_head: methods.remove('HEAD') if 'OPTIONS' in methods and not show_options: methods.remove('OPTIONS') operation = load_yaml_from_docstring(f_view.__doc__) if operation: for method in methods: view_operations[method.lower()] = operation elif autodoc: path_parameters = get_uri_placeholders(uri_pattern) for method in methods: auto_doc = AutoDoc(method, view, cornice_service) if path_parameters: auto_doc.add_path_parameter(path_parameters) request_schema = auto_doc.find_schema_for('body') if request_schema: add_schema_in_spec(spec, request_schema) view_operations = auto_doc.to_dict() operations.update(view_operations) return operations
def path_helper(self, operations, *, view, **kwargs): """Path helper that allows passing a bottle view function.""" operations.update( yaml_utils.load_operations_from_docstring(view.__doc__)) app = kwargs.get("app", _default_app) route = self._route_for_view(app, view) return self.bottle_path_to_openapi(route.rule)
def _apispec(self): info = {} if self.description is not None: info["description"] = self.description if self.terms_of_service is not None: info["termsOfService"] = self.terms_of_service if self.contact is not None: info["contact"] = self.contact if self.license is not None: info["license"] = self.license spec = APISpec( title=self.title, version=self.version, openapi_version=self.openapi_version, plugins=[MarshmallowPlugin()], info=info, ) for route in self.routes: if self.routes[route].description: operations = yaml_utils.load_operations_from_docstring( self.routes[route].description) spec.path(path=route, operations=operations) for name, schema in self.schemas.items(): spec.components.schema(name, schema=schema) return spec
def path_helper(self, operations, resource, base_path=None, **kwargs): """Path helper that allows passing a Falcon resource instance.""" resource_uri_mapping = self._generate_resource_uri_mapping(self._app) if resource not in resource_uri_mapping: raise APISpecError( "Could not find endpoint for resource {0}".format(resource)) operations.update( yaml_utils.load_operations_from_docstring(resource.__doc__) or {}) path = resource_uri_mapping[resource]["uri"] if base_path is not None: # make sure base_path accept either with or without leading slash # swagger 2 usually come with leading slash but not in openapi 3.x.x base_path = '/' + base_path.strip('/') path = re.sub(base_path, "", path, 1) methods = resource_uri_mapping[resource]["methods"] for method_name, method_handler in methods.items(): docstring_yaml = yaml_utils.load_yaml_from_docstring( method_handler.__doc__) operations[method_name] = docstring_yaml or dict() return path
def path_helper(self, resource, operations, path=None, **kwargs) -> str: operations.update(yaml_utils.load_operations_from_docstring(resource.__doc__) or {}) path = path or self.resource_uri_mapping[resource]["uri"] methods = self.resource_uri_mapping[resource]["methods"] for method_name, method_handler in methods.items(): docstring_yaml = yaml_utils.load_yaml_from_docstring(method_handler.__doc__) operations[method_name] = docstring_yaml or {} return path
def path_helper(self, operations, *, view, app, **kwargs): """Path helper that allows passing a chalice view function.""" if not isinstance(app, Chalice): raise APISpecError(f"app must be an instance of Chalice") # parse through the documentation string to see what operations are defined operations.update( yaml_utils.load_operations_from_docstring(view.__doc__)) # find the route for this view function route = self._route_for_view(app, operations, view) # if we didn't throw an exception then the view function handles all operations found in the docs return route
def path_helper(self, operations, view, **kwargs): """Path helper that allows passing a Flask view function.""" rule = self._rule_for_view(view) operations.update(yaml_utils.load_operations_from_docstring(view.__doc__)) if hasattr(view, 'view_class') and issubclass(view.view_class, MethodView): for method in view.methods: if method in rule.methods: method_name = method.lower() method = getattr(view.view_class, method_name) operations[method_name] = yaml_utils.load_yaml_from_docstring(method.__doc__) return self.flaskpath2openapi(rule.rule)
def wrapper(func): # get the file path = Path(spec_path) if not path.exists(): return func # get the content content = path.read_text() # save the content in a special attribute of the function func.__apispec__ = func.__dict__.get("__apispec__", {}) func.__apispec__.update(load_operations_from_docstring(content)) return func
def _apispec(self): spec = APISpec(title=self.title, version=self.version, openapi_version=self.openapi_version, plugins=[MarshmallowPlugin()], **self._apispec_options) for route in self.routes: if self.routes[route].description: operations = yaml_utils.load_operations_from_docstring( self.routes[route].description) spec.path(path=route, operations=operations) for name, schema in self.schemas.items(): spec.components.schema(name, schema=schema) return spec
def path_helper(self, operations, view, **kwargs): """Path helper that allows passing a Flask view function.""" rule = self._rule_for_view(view) operations.update( yaml_utils.load_operations_from_docstring(view.__doc__)) if hasattr(view, 'view_class') and issubclass(view.view_class, MethodView): for method in view.methods: if method in rule.methods: method_name = method.lower() method = getattr(view.view_class, method_name) operations[ method_name] = yaml_utils.load_yaml_from_docstring( method.__doc__) path = self.flaskpath2openapi(rule.rule) app_root = current_app.config['APPLICATION_ROOT'] or '/' return urljoin(app_root.rstrip('/') + '/', path.lstrip('/'))
def path_helper(self, operations, resource, **kwargs): """Path helper that allows passing a Falcon resource instance.""" resource_uri_mapping = self._generate_resource_uri_mapping(self._app) if resource not in resource_uri_mapping: raise APISpecError("Could not find endpoint for resource {0}".format(resource)) operations.update(yaml_utils.load_operations_from_docstring(resource.__doc__) or {}) path = resource_uri_mapping[resource] for method in falcon.constants.HTTP_METHODS: http_verb = method.lower() method_name = "on_" + http_verb if getattr(resource, method_name, None) is not None: method = getattr(resource, method_name) docstring_yaml = yaml_utils.load_yaml_from_docstring(method.__doc__) operations[http_verb] = docstring_yaml or dict() return path
def initialize_routes(api): api.register_blueprint(comics, url_prefix='/comics') api.register_blueprint(docs) with api.test_request_context(): for r in api.url_map.iter_rules(): view = api.view_functions.get(r.endpoint) operations = load_operations_from_docstring(view.__doc__) path = FlaskPlugin.flaskpath2openapi(r.rule) if not operations: continue # De-reference the schemas referenced in the docstring. for verb in operations: resp = operations.get(verb).get('responses') for status in resp: val = resp.get(status) content = resp.get(status).get('schema') if content: pass # Add paths to the spec spec.path(view=view, path=path, operations=operations)
def path_helper(self, path=None, operations: dict = None, parameters: list = None, **kwargs): """Path helper that allows passing a Falcon resource instance.""" uri_to_method_map = self._get_uri_falcon_details_mapping() if path not in uri_to_method_map: raise APISpecError(f"Could not find handlers for path='{path}'") falcon_routing_details = uri_to_method_map[path] resource = falcon_routing_details["resource"] operations.update( yaml_utils.load_operations_from_docstring(resource.__doc__) or {}) methods = falcon_routing_details["methods"] for method_name, method_handler in methods.items(): docstring_yaml = yaml_utils.load_yaml_from_docstring( method_handler.__doc__) operations[method_name] = docstring_yaml or dict() return path
def update_specs(cls, specs): schema_name = cls.meta.name if cls.Schema: schema_name = cls.Schema.__name__.replace('Schema', '') if schema_name not in specs.components._schemas: specs.components.schema(schema_name, schema=cls.Schema) operations = yaml_utils.load_operations_from_docstring(cls.__doc__) openapi_url = convert_flask_url_to_openapi(cls.meta.url) path_parameters = cls.build_path_parameters(openapi_url) operations_spec = cls.update_operations_specs( operations, ('GET', 'POST'), detail=None if not cls.meta.url_detail else False, parameters=path_parameters) specs.path(openapi_url, operations=operations_spec) if cls.meta.url_detail: openapi_url = convert_flask_url_to_openapi(cls.meta.url_detail) path_parameters = cls.build_path_parameters(openapi_url) operations_spec = cls.update_operations_specs( operations, ('GET', 'PUT', 'PATCH', 'DELETE'), detail=True, parameters=path_parameters) specs.path(openapi_url, operations=operations_spec) for endpoint, (url_, name_, params_) in cls.meta.endpoints.values(): openapi_url = convert_flask_url_to_openapi( f"{cls.meta.url.rstrip('/')}/{url_}") path_parameters = cls.build_path_parameters(openapi_url) operations_spec = cls.update_operations_specs( operations, params_.get('methods', ('GET', )), method=getattr(cls, name_, None), detail=None, parameters=path_parameters) specs.path(openapi_url, operations=operations_spec)
def test_from_file_decorator(tmp_path): """ Test the from_file decorator. """ # create a temp yaml file yaml_content = """ --- get: summary: Hello operationId: hello responses: 200: content: application/json: schema: type: string """ yaml_file = tmp_path / "hello.yml" yaml_file.write_text(yaml_content) # decorate a function with the from_file decorator @from_file(str(yaml_file)) def hello(): return {"hello"} assert load_operations_from_docstring(yaml_content) == hello.__apispec__
def path_helper(self, operations, resource, base_path=None, **kwargs): """Path helper that allows passing a Falcon resource instance.""" resource_uri_mapping = self._generate_resource_uri_mapping(self._app) if resource not in resource_uri_mapping: raise APISpecError("Could not find endpoint for resource {0}".format(resource)) operations.update(yaml_utils.load_operations_from_docstring(resource.__doc__) or {}) path = resource_uri_mapping[resource] if base_path is not None: # make sure base_path accept either with or without leading slash # swagger 2 usually come with leading slash but not in openapi 3.x.x base_path = '/' + base_path.strip('/') path = re.sub(base_path, "", path, 1) for method in falcon.constants.HTTP_METHODS: http_verb = method.lower() method_name = "on_" + http_verb if getattr(resource, method_name, None) is not None: method = getattr(resource, method_name) docstring_yaml = yaml_utils.load_yaml_from_docstring(method.__doc__) operations[http_verb] = docstring_yaml or dict() return path
def test_load_operations_from_docstring_empty_docstring(docstring): assert yaml_utils.load_operations_from_docstring(docstring) == {}
async def get_spec(socket: Socket, request_id: str) -> APISpec: """ Construct open api spec by interrogating FlowMachine. Parameters ---------- socket : Socket request_id : str Unique id of the request Returns ------- APISpec The specification object """ msg = {"request_id": request_id, "action": "get_query_schemas"} socket.send_json(msg) # Get the reply. reply = await socket.recv_json() flowmachine_query_schemas = reply["payload"]["query_schemas"] # Need to mark query_kind as a required field # this is a workaround because the marshmallow-oneOf plugin strips # the query_kind off, which means it can't be required from the marshmallow # side without raising an error for schema, schema_dict in flowmachine_query_schemas.items(): try: if "query_kind" in schema_dict["properties"]: schema_dict["required"].append("query_kind") except KeyError: pass # Doesn't have any properties spec = APISpec( title="FlowAPI", version=__version__, openapi_version="3.0.1", info=dict( description="FlowKit Analytical API", license=dict(name="MPLv2", url="https://www.mozilla.org/en-US/MPL/2.0/"), contact=dict(email="*****@*****.**"), ), ) spec.components.schemas.update(flowmachine_query_schemas) spec.components.security_scheme( "token", { "type": "http", "scheme": "bearer", "bearerFormat": "JWT", "x-security-scopes": sorted(schema_to_scopes(spec.to_dict())), "x-audience": current_app.config["JWT_DECODE_AUDIENCE"], }, ) # Loop over all the registered views and try to parse a yaml # openapi spec from their docstrings for rule in current_app.url_map.iter_rules(): try: func = current_app.view_functions[rule.endpoint] operations = yaml_utils.load_operations_from_docstring( func.__doc__) if len(operations) > 0: for method, op in operations.items(): op["operationId"] = f"{rule.endpoint}.{method}" spec.path( path=rule.rule, operations=operations, ) except Exception as e: pass # Don't include in API return spec
async def get_spec(socket: Socket, request_id: str) -> APISpec: """ Construct open api spec by interrogating FlowMachine. Parameters ---------- socket : Socket request_id : str Unique id of the request Returns ------- APISpec The specification object """ msg = {"request_id": request_id, "action": "get_query_schemas"} socket.send_json(msg) # Get the reply. reply = await socket.recv_json() flowmachine_query_schemas = reply["payload"]["query_schemas"] # Need to mark query_kind as a required field # this is a workaround because the marshmallow-oneOf plugin strips # the query_kind off, which means it can't be required from the marshmallow # side without raising an error for schema, schema_dict in flowmachine_query_schemas.items(): try: schema_dict["required"].append("query_kind") except KeyError: pass # Doesn't have any properties spec = APISpec( title="FlowAPI", version=__version__, openapi_version="3.0.1", info=dict( description="FlowKit Analytical API", license=dict(name="MPLv2", url="https://www.mozilla.org/en-US/MPL/2.0/"), contact=dict(email="*****@*****.**"), ), ) spec.components._schemas = flowmachine_query_schemas spec.components.security_scheme( "token", dict(type="http", scheme="bearer", bearerFormat="JWT") ) # Loop over all the registered views and try to parse a yaml # openapi spec from their docstrings for endpoint_func_name, rule in current_app.url_map.endpoints.items(): try: func = current_app.view_functions[endpoint_func_name] operations = yaml_utils.load_operations_from_docstring(func.__doc__) if len(operations) > 0: for method, op in operations.items(): op["operationId"] = f"{endpoint_func_name}.{method}" spec.path( path=rule[ 0 ].rule, # In theory, could have multiple rules that match but will only be a single one here operations=operations, ) except Exception as e: pass # Don't include in API return spec
def operation_helper(self, path, operations, router, func, **kwargs): new_operations = load_operations_from_docstring(func.__doc__) if hasattr(func, '_cp_config') is False: return None cp_config = func._cp_config if new_operations is not None: for method, data in new_operations.items(): if cp_config.get('tools.authentication.on', True): data['security'] = [{'Bearer': []}] if 'tools.model_in.cls' in cp_config: model_cls = cp_config['tools.model_in.cls'] try: self.spec.components.schema(model_cls.__name__, component=parse_model( self.spec, model_cls)) except DuplicateComponentNameError: pass data['requestBody']['required'] = True data['requestBody']['content'] = { 'application/json': { 'schema': { '$ref': '#/components/schemas/' + model_cls.__name__ } } } if 'tools.model_params.cls' in cp_config: model_cls = cp_config['tools.model_params.cls'] data['parameters'] = data.get('parameters', []) # In query vs in path for key, obj in model_cls.__dict__.items(): inn = 'query' if '{' + key + '}' in path: inn = 'path' if isinstance(obj, FieldDescriptor): paramenters = { 'name': key, 'in': inn, 'required': model_cls._fields[key].required, 'schema': parse_model_type(self.spec, model_cls._fields[key]), } if isinstance(model_cls._fields[key]._default, UndefinedType) is False: paramenters['schema'][ 'default'] = model_cls._fields[ key]._default data['parameters'].append(paramenters) if 'tools.model_out.cls' in cp_config: model_cls = cp_config['tools.model_out.cls'] try: self.spec.components.schema(model_cls.__name__, component=parse_model( self.spec, model_cls)) except DuplicateComponentNameError: pass data['responses'][200]['content'] = { 'application/json': { 'schema': { '$ref': '#/components/schemas/' + model_cls.__name__ } } } if 'tools.model_out_pagination.cls' in cp_config: model_cls = cp_config['tools.model_out_pagination.cls'] try: self.spec.components.schema(model_cls.__name__, component=parse_model( self.spec, model_cls)) except DuplicateComponentNameError: pass self.spec.components.schema( "List" + model_cls.__name__, component={ 'type': 'object', 'properties': { path.split("/")[-1]: { 'type': 'array', 'items': { '$ref': '#/components/schemas/' + model_cls.__name__ } }, path.split("/")[-1] + "_links": { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'href': { 'type': 'string' }, 'rel': { 'type': 'string' } } } } }, }) data['responses'][200]['content'] = { 'application/json': { 'schema': { '$ref': '#/components/schemas/' + "List" + model_cls.__name__ } } } if 'tools.enforce_permission.permission_name' in cp_config: data['x-required-permission'] = cp_config[ 'tools.enforce_permission.permission_name'] operations.update(new_operations) return None
def path_helper(self, operations, *, view, app=None, **kwargs): """Path helper that allows passing a Flask view function.""" rule = self._rule_for_view(view) operations.update( yaml_utils.load_operations_from_docstring(view.__doc__)) return self.flaskpath2openapi(rule.rule)
async def generate_openapi_spec(app: web.Application) -> None: accepted_methods = {METH_GET, METH_PUT, METH_POST, METH_DELETE, METH_PATCH} paths = {} schemas = [] options = { "info": { "description": "[Cookie Authentication](https://swagger.io/docs/specification/authentication/cookie-authentication/) " "[OAuth2 Implicit Grant and SPA](https://auth0.com/blog/oauth2-implicit-grant-and-spa/)" }, "securitySchemes": { "cookieAuth": { "type": "apiKey", "in": "cookie", "name": app["settings"]["session"]["cookie"]["cookie_name"] } } } spec = APISpec(title=app["settings"]["name"], version=app["settings"]["version"], openapi_version="3.0.2", plugins=[MarshmallowPlugin()], **options) routes = [ route for route in app.router.routes() if route.name != app["settings"]["openapi"]["route"]["name"] ] for route in routes: operations: Dict = {} if issubclass_py37(route.handler, web.View) and route.method == METH_ANY: for attr in dir(route.handler): attr_uc = attr.upper() if attr_uc in METH_ALL and attr_uc in accepted_methods: docstring_source = getattr(route.handler, attr) operations = load_operations_from_docstring( docstring_source.__doc__) else: docstring_source = route.handler operations = load_operations_from_docstring( docstring_source.__doc__) if operations: info = route.get_info() path = info.get('path') or info.get('formatter') for schema in lookup("schema", operations): if schema not in schemas: schemas.append(schema) if path not in paths.keys(): paths[path] = {} paths[path].update(operations) for schema in schemas: schema_class = getattr(schemas_module, schema) if callable(schema_class): schema_name = re.match(r"([^/]+?)(?:Schema)*$", schema).group(1) spec.components.schema(schema_name, schema=schema_class) for path, operations in paths.items(): spec.path(path=path, operations=operations) app["openapi"] = spec
def operation_helper(self, operations, func, **kwargs): """Operation helper that parses docstrings for operations. Adds a ``func`` parameter to `apispec.APISpec.path`. """ doc_operations = load_operations_from_docstring(func.__doc__) operations.update(doc_operations)