def __init__(self, import_name, name, prefix, template_folder=None):
        self.blueprint = Blueprint(import_name=import_name,
                                   name=name,
                                   url_prefix=prefix,
                                   template_folder=template_folder)
        self.prefix = prefix
        self.name = name
        self.parser = FlaskParser()
        self.apidoc = {}

        @self.parser.error_handler
        def _handle_error(err):
            raise err
def search_results():
    form = SearchForm(request.args)

    parser = FlaskParser()
    search_kwargs = parser.parse(search_args, request)
    if search_kwargs.get('page_size') is None or search_kwargs.get('page_size') == '':
        search_kwargs['page_size'] = '25'
    if search_kwargs.get('page') is None or search_kwargs.get('page') == '':
        search_kwargs['page'] = '1'
    if (search_kwargs.get('page_number') is None or search_kwargs.get('page_number') == '') \
            and search_kwargs.get('page') is not None:
        search_kwargs['page_number'] = search_kwargs['page']


    sp = SearchPublications(search_url)
    search_results_response, resp_status_code = sp.get_pubs_search_results(
        params=search_kwargs)  # go out to the pubs API and get the search results
    try:
        search_result_records = search_results_response['records']
        record_count = search_results_response['recordCount']
        pagination = Pagination(page=int(search_kwargs['page_number']), total=record_count,
                                per_page=int(search_kwargs['page_size']), record_name='Search Results', bs_version=3)
        search_service_down = None
        start_plus_size = int(search_results_response['pageRowStart']) + int(search_results_response['pageSize'])
        if record_count < start_plus_size:
            record_max = record_count
        else:
            record_max = start_plus_size

        result_summary = {'record_count': record_count, 'page_number': search_results_response['pageNumber'],
                          'records_per_page': search_results_response['pageSize'],
                          'record_min': (int(search_results_response['pageRowStart']) + 1), 'record_max': record_max}
    except TypeError:
        search_result_records = None
        pagination = None
        search_service_down = 'The backend services appear to be down with a {0} status.'.format(resp_status_code)
        result_summary = {}
    if 'mimetype' in request.args and request.args.get("mimetype") == 'ris':
        content = render_template('pubswh/ris_output.ris', search_result_records=search_result_records)
        return Response(content, mimetype="application/x-research-info-systems",
                               headers={"Content-Disposition":"attachment;filename=PubsWarehouseResults.ris"})
    if request.args.get('map') == 'True':
        for record in search_result_records:
            record = jsonify_geojson(record)

    return render_template('pubswh/search_results.html',
                           result_summary=result_summary,
                           search_result_records=search_result_records,
                           pagination=pagination,
                           search_service_down=search_service_down,
                           form=form, pub_url=pub_url)
def search_results():
    parser = FlaskParser()
    search_kwargs = parser.parse(search_args, request)
    form = SearchForm(None, obj=request.args, )
    # populate form based on parameter
    form.advanced.data = True
    form_element_list = ['q', 'title', 'contributingOffice', 'contributor', 'typeName', 'subtypeName', 'seriesName',
                         'reportNumber', 'year']
    for element in form_element_list:
        if len(search_kwargs[element]) > 0:
            form[element].data = search_kwargs[element][0]
    if search_kwargs.get('page_size') is None or search_kwargs.get('page_size') == '':
        search_kwargs['page_size'] = '25'
    if search_kwargs.get('page') is None or search_kwargs.get('page') == '':
        search_kwargs['page'] = '1'
    if (search_kwargs.get('page_number') is None or search_kwargs.get('page_number') == '') \
            and search_kwargs.get('page') is not None:
        search_kwargs['page_number'] = search_kwargs['page']


    sp = SearchPublications(search_url)
    search_results_response, resp_status_code = sp.get_pubs_search_results(
        params=search_kwargs)  # go out to the pubs API and get the search results
    try:
        search_result_records = search_results_response['records']
        record_count = search_results_response['recordCount']
        pagination = Pagination(page=int(search_kwargs['page_number']), total=record_count,
                                per_page=int(search_kwargs['page_size']), record_name='Search Results', bs_version=3)
        search_service_down = None
        start_plus_size = int(search_results_response['pageRowStart']) + int(search_results_response['pageSize'])
        if record_count < start_plus_size:
            record_max = record_count
        else:
            record_max = start_plus_size

        result_summary = {'record_count': record_count, 'page_number': search_results_response['pageNumber'],
                          'records_per_page': search_results_response['pageSize'],
                          'record_min': (int(search_results_response['pageRowStart']) + 1), 'record_max': record_max}
    except TypeError:
        search_result_records = None
        pagination = None
        search_service_down = 'The backend services appear to be down with a {0} status.'.format(resp_status_code)
        result_summary = {}
    return render_template('search_results.html',
                           advanced=search_kwargs['advanced'],
                           result_summary=result_summary,
                           search_result_records=search_result_records,
                           pagination=pagination,
                           search_service_down=search_service_down,
                           form=form)
Exemple #4
0
 def _parse_data(self, schema, request, *args, **kwargs):
     """Deserializes from a Flask request to a dict with valid
     data. It a ``Marshmallow.Schema`` instance to perform the
     deserialization
     """
     return FlaskParser().parse(schema, request, locations=('json',),
                                *args, **kwargs)
Exemple #5
0
    def post(self):
        """Uploads a file"""

        file_obj = FlaskParser.parse_files(self,
                                           request,
                                           "upfile",
                                           field=fields.Raw())  # pylint: disable=maybe-no-member

        if isinstance(file_obj, type(
                marshmallow.missing)):  # werkzeug.datastructures.FileStorage
            return {"message": "File missing"}, 400  # Bad request

        size = Upload.get_size(file_obj)

        if size > app.config["MAX_UPLOAD"]:
            return {
                "message":
                "You exceded file limit of {}".format(app.config["MAX_UPLOAD"])
            }, 400

        if file_obj.mimetype != Upload.mimetype:
            return {
                "message":
                "File mimetype is {} instead of {}".format(
                    str(file_obj.mimetype), Upload.mimetype)
            }, 415  # 415 Unsupported Media Type

        file_path = os.path.join(app.config['UPLOAD_FOLDER'],
                                 file_obj.filename)
        file_obj.save(file_path)

        return jsonify(
            message="Saved file:{} with size {} bytes".format(file_path, size))
class ArgumentsMixin:
    """Extend Blueprint to add arguments parsing feature"""

    ARGUMENTS_PARSER = FlaskParser()

    def arguments(
            self, schema, *, location='json', required=True,
            example=None, examples=None, **kwargs
    ):
        """Decorator specifying the schema used to deserialize parameters

        :param type|Schema schema: Marshmallow ``Schema`` class or instance
            used to deserialize and validate the argument.
        :param str location: Location of the argument.
        :param bool required: Whether argument is required (default: True).
            This only affects `body` arguments as, in this case, the docs
            expose the whole schema as a `required` parameter.
            For other locations, the schema is turned into an array of
            parameters and their required value is inferred from the schema.
        :param dict example: Parameter example.
        :param list examples: List of parameter examples.
        :param dict kwargs: Keyword arguments passed to the webargs
            :meth:`use_args <webargs.core.Parser.use_args>` decorator used
            internally.

        The `example` and `examples` parameters are mutually exclusive and
        should only be used with OpenAPI 3 and when location is `json`.

        See :doc:`Arguments <arguments>`.
        """
        # At this stage, put schema instance in doc dictionary. Il will be
        # replaced later on by $ref or json.
        parameters = {
            'in': location,
            'required': required,
            'schema': schema,
        }
        if example is not None:
            parameters['example'] = example
        if examples is not None:
            parameters['examples'] = examples

        def decorator(func):

            @wraps(func)
            def wrapper(*f_args, **f_kwargs):
                return func(*f_args, **f_kwargs)

            # Add parameter to parameters list in doc info in function object
            # The deepcopy avoids modifying the wrapped function doc
            wrapper._apidoc = deepcopy(getattr(wrapper, '_apidoc', {}))
            wrapper._apidoc.setdefault('parameters', []).append(parameters)

            # Call use_args (from webargs) to inject params in function
            return self.ARGUMENTS_PARSER.use_args(
                schema, locations=[location], **kwargs)(wrapper)

        return decorator
class OdataMixin:
    """Extend Blueprint to add Odata feature"""

    ODATA_ARGUMENTS_PARSER = FlaskParser()

    def odata(self, session: Session, default_orderby: str = None):
        """Decorator adding odata capability to endpoint."""

        parameters = {
            'in': 'query',
            'schema': OdataSchema,
        }

        error_status_code = (
            self.ODATA_ARGUMENTS_PARSER.DEFAULT_VALIDATION_STATUS)

        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                odata_params = self.ODATA_ARGUMENTS_PARSER.parse(
                    OdataSchema, request, location='query')
                # Execute decorated function
                model, status, headers = unpack_tuple_response(
                    func(*args, **kwargs))
                # Apply Odata
                query = Odata(
                    session=session,
                    model=model,
                    odata_parameters=odata_params,
                    default_orderby=default_orderby,
                ).query
                return query, status, headers

            # Add odata params to doc info in wrapper object
            wrapper._apidoc = deepcopy(getattr(wrapper, '_apidoc', {}))
            wrapper._apidoc['odata'] = {
                'parameters': parameters,
                'response': {
                    error_status_code: HTTPStatus(error_status_code).name,
                }
            }

            return wrapper

        return decorator

    def _prepare_odata_doc(self, doc, doc_info, *, spec, **kwargs):
        operation = doc_info.get('odata')
        if operation:
            doc.setdefault('parameters', []).append(operation['parameters'])
            doc.setdefault('responses', {}).update(operation['response'])
        return doc
Exemple #8
0
    def _get_args(cls, **kwargs):
        """Parse style and locale.

        Argument location precedence: kwargs > view_args > query
        """
        csl_args = {'style': cls._default_style, 'locale': cls._default_locale}

        if has_request_context():
            parser = FlaskParser(locations=('view_args', 'query'))
            csl_args.update(parser.parse(cls._user_args, request))

        csl_args.update(
            {k: kwargs[k]
             for k in ('style', 'locale') if k in kwargs})

        try:
            csl_args['style'] = get_style_filepath(csl_args['style'].lower())
        except StyleNotFoundError:
            if has_request_context():
                raise StyleNotFoundRESTError(csl_args['style'])
            raise
        return csl_args
Exemple #9
0
class ArgumentsMixin:
    """Extend Blueprint to add arguments parsing feature"""

    ARGUMENTS_PARSER = FlaskParser()

    def arguments(self, schema, *, location='json', required=True, **kwargs):
        """Decorator specifying the schema used to deserialize parameters

        :param type|Schema schema: Marshmallow ``Schema`` class or instance
            used to deserialize and validate the argument.
        :param str location: Location of the argument.
        :param bool required: Whether argument is required (default: True).
            This only affects `body` arguments as, in this case, the docs
            expose the whole schema as a `required` parameter.
            For other locations, the schema is turned into an array of
            parameters and their required value is inferred from the schema.
        :param dict kwargs: Keyword arguments passed to the webargs
            :meth:`use_args <webargs.core.Parser.use_args>` decorator used
            internally.

        See :doc:`Arguments <arguments>`.
        """
        # TODO: This shouldn't be needed. I think I did this because apispec
        # worked better with instances, but this should have been solved since.
        if isinstance(schema, type):
            schema = schema()

        try:
            openapi_location = __location_map__[location]
        except KeyError:
            raise InvalidLocation(
                "{} is not a valid location".format(location))

        # At this stage, put schema instance in doc dictionary. Il will be
        # replaced later on by $ref or json.
        parameters = {
            'in': openapi_location,
            'required': required,
            'schema': schema,
        }

        def decorator(func):
            # Add parameter to parameters list in doc info in function object
            func._apidoc = getattr(func, '_apidoc', {})
            func._apidoc.setdefault('parameters', []).append(parameters)
            # Call use_args (from webargs) to inject params in function
            return self.ARGUMENTS_PARSER.use_args(schema,
                                                  locations=[location],
                                                  **kwargs)(func)

        return decorator
    def _get_args(cls, **kwargs):
        """Parse style and locale.

        Argument location precedence: kwargs > view_args > query
        """
        csl_args = {
            'style': cls._default_style,
            'locale': cls._default_locale
        }

        if has_request_context():
            parser = FlaskParser(locations=('view_args', 'query'))
            csl_args.update(parser.parse(cls._user_args, request))

        csl_args.update({k: kwargs[k]
                         for k in ('style', 'locale') if k in kwargs})

        try:
            csl_args['style'] = get_style_filepath(csl_args['style'].lower())
        except StyleNotFoundError:
            if has_request_context():
                raise StyleNotFoundRESTError(csl_args['style'])
            raise
        return csl_args
Exemple #11
0
        def wrapper(self, *args, **kwargs):

            args = FlaskParser().parse(
                {
                    'page': Arg(int, default=1),
                    'limit': Arg(int, default=limit)
                }, request)

            if args['limit'] > maximum:
                args['limit'] = maximum

            response = function(self,
                                limit=args['limit'],
                                page=args['page'],
                                offset=(args['page'] - 1) * args['limit'],
                                *args,
                                **kwargs)

            return response
class PaginationMixin:
    """Extend Blueprint to add Pagination feature"""

    PAGINATION_ARGUMENTS_PARSER = FlaskParser()

    # Name of field to use for pagination metadata response header
    # Can be overridden. If None, no pagination header is returned.
    PAGINATION_HEADER_FIELD_NAME = 'X-Pagination'

    # Global default pagination parameters
    # Can be overridden to provide custom defaults
    DEFAULT_PAGINATION_PARAMETERS = {
        'page': 1,
        'page_size': 10,
        'max_page_size': 100
    }

    PAGINATION_HEADER_DOC = {
        'description': 'Pagination metadata',
        'schema': PaginationHeaderSchema,
    }

    def paginate(self,
                 pager=None,
                 *,
                 page=None,
                 page_size=None,
                 max_page_size=None):
        """Decorator adding pagination to the endpoint

        :param Page pager: Page class used to paginate response data
        :param int page: Default requested page number (default: 1)
        :param int page_size: Default requested page size (default: 10)
        :param int max_page_size: Maximum page size (default: 100)

        If a :class:`Page <Page>` class is provided, it is used to paginate the
        data returned by the view function, typically a lazy database cursor.

        Otherwise, pagination is handled in the view function.

        The decorated function may return a tuple including status and/or
        headers, like a typical flask view function. It may not return a
        ``Response`` object.

        See :doc:`Pagination <pagination>`.
        """
        if page is None:
            page = self.DEFAULT_PAGINATION_PARAMETERS['page']
        if page_size is None:
            page_size = self.DEFAULT_PAGINATION_PARAMETERS['page_size']
        if max_page_size is None:
            max_page_size = self.DEFAULT_PAGINATION_PARAMETERS['max_page_size']
        page_params_schema = _pagination_parameters_schema_factory(
            page, page_size, max_page_size)

        parameters = {
            'in': 'query',
            'schema': page_params_schema,
        }

        error_status_code = (
            self.PAGINATION_ARGUMENTS_PARSER.DEFAULT_VALIDATION_STATUS)

        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):

                page_params = self.PAGINATION_ARGUMENTS_PARSER.parse(
                    page_params_schema, request, location='query')

                # Pagination in resource code: inject page_params as kwargs
                if pager is None:
                    kwargs['pagination_parameters'] = page_params

                # Execute decorated function
                result, status, headers = unpack_tuple_response(
                    func(*args, **kwargs))

                # Post pagination: use pager class to paginate the result
                if pager is not None:
                    result = pager(result, page_params=page_params).items

                # Add pagination metadata to headers
                if self.PAGINATION_HEADER_FIELD_NAME is not None:
                    if page_params.item_count is None:
                        current_app.logger.warning(
                            'item_count not set in endpoint {}'.format(
                                request.endpoint))
                    else:
                        page_header = self._make_pagination_header(
                            page_params.page, page_params.page_size,
                            page_params.item_count)
                        if headers is None:
                            headers = {}
                        headers[
                            self.PAGINATION_HEADER_FIELD_NAME] = page_header

                return result, status, headers

            # Add pagination params to doc info in wrapper object
            wrapper._apidoc = deepcopy(getattr(wrapper, '_apidoc', {}))
            wrapper._apidoc['pagination'] = {
                'parameters': parameters,
                'response': {
                    error_status_code: http.HTTPStatus(error_status_code).name,
                }
            }
            wrapper._paginated = True

            return wrapper

        return decorator

    @staticmethod
    def _make_pagination_header(page, page_size, item_count):
        """Build pagination header from page, page size and item count

        This method returns a json representation of a default pagination
        metadata structure. It can be overridden to use another structure.
        """
        page_header = OrderedDict()
        page_header['total'] = item_count
        if item_count == 0:
            page_header['total_pages'] = 0
        else:
            # First / last page, page count
            page_count = ((item_count - 1) // page_size) + 1
            first_page = 1
            last_page = page_count
            page_header['total_pages'] = page_count
            page_header['first_page'] = first_page
            page_header['last_page'] = last_page
            # Page, previous / next page
            if page <= last_page:
                page_header['page'] = page
                if page > first_page:
                    page_header['previous_page'] = page - 1
                if page < last_page:
                    page_header['next_page'] = page + 1
        header = PaginationHeaderSchema().dumps(page_header)
        if MARSHMALLOW_VERSION_MAJOR < 3:
            header = header.data
        return header

    @staticmethod
    def _prepare_pagination_doc(doc, doc_info, **kwargs):
        operation = doc_info.get('pagination')
        if operation:
            parameters = operation.get('parameters')
            doc.setdefault('parameters', []).append(parameters)
            response = operation.get('response')
            doc.setdefault('responses', {}).update(response)
        return doc
Exemple #13
0
class PaginationMixin:
    """Extend Blueprint to add Pagination feature"""

    PAGINATION_ARGUMENTS_PARSER = FlaskParser()

    # Name of field to use for pagination metadata response header
    # Can be overridden. If None, no pagination header is returned.
    PAGINATION_HEADER_NAME = "X-Pagination"

    # Global default pagination parameters
    # Can be overridden to provide custom defaults
    DEFAULT_PAGINATION_PARAMETERS = {
        "page": 1,
        "page_size": 10,
        "max_page_size": 100
    }

    def paginate(self,
                 pager=None,
                 *,
                 page=None,
                 page_size=None,
                 max_page_size=None):
        """Decorator adding pagination to the endpoint

        :param Page pager: Page class used to paginate response data
        :param int page: Default requested page number (default: 1)
        :param int page_size: Default requested page size (default: 10)
        :param int max_page_size: Maximum page size (default: 100)

        If a :class:`Page <Page>` class is provided, it is used to paginate the
        data returned by the view function, typically a lazy database cursor.

        Otherwise, pagination is handled in the view function.

        The decorated function may return a tuple including status and/or
        headers, like a typical flask view function. It may not return a
        ``Response`` object.

        See :doc:`Pagination <pagination>`.
        """
        if page is None:
            page = self.DEFAULT_PAGINATION_PARAMETERS["page"]
        if page_size is None:
            page_size = self.DEFAULT_PAGINATION_PARAMETERS["page_size"]
        if max_page_size is None:
            max_page_size = self.DEFAULT_PAGINATION_PARAMETERS["max_page_size"]
        page_params_schema = _pagination_parameters_schema_factory(
            page, page_size, max_page_size)

        parameters = {
            "in": "query",
            "schema": page_params_schema,
        }

        error_status_code = self.PAGINATION_ARGUMENTS_PARSER.DEFAULT_VALIDATION_STATUS

        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):

                page_params = self.PAGINATION_ARGUMENTS_PARSER.parse(
                    page_params_schema, request, location="query")

                # Pagination in resource code: inject page_params as kwargs
                if pager is None:
                    kwargs["pagination_parameters"] = page_params

                # Execute decorated function
                result, status, headers = unpack_tuple_response(
                    func(*args, **kwargs))

                # Post pagination: use pager class to paginate the result
                if pager is not None:
                    result = pager(result, page_params=page_params).items

                # Set pagination metadata in response
                if self.PAGINATION_HEADER_NAME is not None:
                    if page_params.item_count is None:
                        warnings.warn(
                            "item_count not set in endpoint {}.".format(
                                request.endpoint))
                    else:
                        result, headers = self._set_pagination_metadata(
                            page_params, result, headers)

                return result, status, headers

            # Add pagination params to doc info in wrapper object
            wrapper._apidoc = deepcopy(getattr(wrapper, "_apidoc", {}))
            wrapper._apidoc["pagination"] = {
                "parameters": parameters,
                "response": {
                    error_status_code: http.HTTPStatus(error_status_code).name,
                },
            }

            return wrapper

        return decorator

    @staticmethod
    def _make_pagination_metadata(page, page_size, item_count):
        """Build pagination metadata from page, page size and item count

        Override this to use another pagination metadata structure
        """
        page_metadata = {}
        page_metadata["total"] = item_count
        if item_count == 0:
            page_metadata["total_pages"] = 0
        else:
            # First / last page, page count
            page_count = ((item_count - 1) // page_size) + 1
            first_page = 1
            last_page = page_count
            page_metadata["total_pages"] = page_count
            page_metadata["first_page"] = first_page
            page_metadata["last_page"] = last_page
            # Page, previous / next page
            if page <= last_page:
                page_metadata["page"] = page
                if page > first_page:
                    page_metadata["previous_page"] = page - 1
                if page < last_page:
                    page_metadata["next_page"] = page + 1
        return PaginationMetadataSchema().dump(page_metadata)

    def _set_pagination_metadata(self, page_params, result, headers):
        """Add pagination metadata to headers

        Override this to set pagination data another way
        """
        if headers is None:
            headers = {}
        headers[self.PAGINATION_HEADER_NAME] = json.dumps(
            self._make_pagination_metadata(page_params.page,
                                           page_params.page_size,
                                           page_params.item_count))
        return result, headers

    def _document_pagination_metadata(self, spec, resp_doc):
        """Document pagination metadata header

        Override this to document custom pagination metadata
        """
        resp_doc["headers"] = {
            self.PAGINATION_HEADER_NAME:
            "PAGINATION"
            if spec.openapi_version.major >= 3 else PAGINATION_HEADER
        }

    def _prepare_pagination_doc(self, doc, doc_info, *, spec, **kwargs):
        operation = doc_info.get("pagination")
        if operation:
            doc.setdefault("parameters", []).append(operation["parameters"])
            doc.setdefault("responses", {}).update(operation["response"])
            success_status_codes = doc_info.get("success_status_codes", [])
            for success_status_code in success_status_codes:
                self._document_pagination_metadata(
                    spec, doc["responses"][success_status_code])
        return doc
class BaseController:
    REQUIRED_ENV = None
    AUTH_BACKEND = None

    def __init__(self, import_name, name, prefix, template_folder=None):
        self.blueprint = Blueprint(import_name=import_name,
                                   name=name,
                                   url_prefix=prefix,
                                   template_folder=template_folder)
        self.prefix = prefix
        self.name = name
        self.parser = FlaskParser()
        self.apidoc = {}

        @self.parser.error_handler
        def _handle_error(err):
            raise err

    def bind(self, core):
        """ 将该controller绑定到app上,并注册相关事件 """
        app = core.app
        if self.REQUIRED_ENV:
            env = app.config['ENVIRONMENT']
            if env != self.REQUIRED_ENV:
                return False
        app.register_blueprint(self.blueprint)
        return True

    def _using_apidoc(self, fn):
        """ 判断当前视图函数是否开启apidoc """
        name = getattr(fn, '__name__', None)
        return name in self.apidoc

    def _route(self, route, method, **options):
        """ 绑定视图函数到blueprint上 """
        def decorator(fn):
            # 检查apidoc
            if self._using_apidoc(fn):
                doc = self.apidoc[fn.__name__]
                doc['method'] = method
                doc['route'] = self.prefix + route

            options['methods'] = [method]
            return self.blueprint.route(route, **options)(fn)

        return decorator

    def get(self, route, **options):
        return self._route(route, 'GET', **options)

    def post(self, route, **options):
        return self._route(route, 'POST', **options)

    def login_required(self):
        """ 校验存在已验证用户
        如果检查到user为BannedUser的实例时,意味着用户被禁止访问
        """
        def decorator(fn):
            @wraps(fn)
            def wrap(*args, **kwargs):
                if not current_user:
                    return self.handle_unauthorization()

                if isinstance(self.AUTH_BACKEND, str):
                    backend = self.AUTH_BACKEND
                elif isinstance(self.AUTH_BACKEND, type) and issubclass(
                        self.AUTH_BACKEND, AbstractBackend):
                    backend = self.AUTH_BACKEND.NAME
                else:
                    backend = str(self.AUTH_BACKEND)
                if getattr(g, 'backend', None) and self.AUTH_BACKEND:
                    if g.backend != backend:
                        return self.handle_unauthorization()

                if isinstance(current_user, BannedUser):
                    return self.handle_banned_user()

                return fn(*args, **kwargs)

            return wrap

        return decorator

    def visit_limit(self, seconds):
        """ 访问限制
        根据通过验证的user id作为key一部分,所以要使用在login_required之后
        """
        def decorator(fn):
            @wraps(fn)
            def wrap(*args, **kwargs):
                if not current_user:
                    return self.handle_unauthorization()

                cache = current_app.core.cache
                user_id = current_user.id
                fn_key = f'{self.name}_{fn.__name__}'
                key = CacheKey('visit_limit', user_id, fn_key)

                if cache.nget(key):
                    if cache.ttl(key) == -1:
                        cache.expire(key, seconds)
                    return self.handle_visit_limit()

                rst = cache.nincr(key)
                cache.expire(key, seconds)
                if rst > 1:
                    return self.handle_visit_limit()

                return fn(*args, **kwargs)

            return wrap

        return decorator

    def use_args(self, fields, code, validate=None, location='form'):
        """ 校验请求参数

        :param fields: 检验字段
        :param code: 检验错误时错误码
        :param validate: 对整个表单的校验
        :param location: 检验参数所在位置(form, query, json, headers...)
        :return:
        """
        assert location in ['query', 'form'], \
            'location should use "query" or "form"'

        def decorator(fn):
            # 检查apidoc
            if self._using_apidoc(fn):
                _args = self.apidoc[fn.__name__].get('args', {})
                _location = _args.get(location, {})
                _location.update(fields)
                _args[location] = _location
                self.apidoc[fn.__name__]['args'] = _args
                self._add_error_info(code, fn.__name__)

            @wraps(fn)
            def wrap(*args, **kwargs):
                try:
                    result = self.parser.parse(fields,
                                               validate=validate,
                                               locations=('querystring',
                                                          'form', 'json'))
                    if 'parsed' in kwargs:
                        old = kwargs.pop('parsed')
                        old.update(result)
                        result = old
                    return fn(parsed=result, *args, **kwargs)
                except ValidationError as err:
                    return self.handle_bad_arguments(err.messages)

            return wrap

        return decorator

    def add_apidoc(self, errors=None, response=None):
        """ 开启api文档

        :param errors: controller自身会自动添加一部分信息于errors中
        :param response: 请求成功后返回的格式
        """
        def decorator(fn):
            response_doc = response or {}
            response_doc['code'] = 0

            api_name = fn.__name__
            self.apidoc[api_name] = {
                'doc':
                fn.__doc__ or '',
                'errors':
                errors or dict(),
                'response':
                json.dumps(response_doc,
                           indent=2,
                           sort_keys=True,
                           default=lambda o: o.__dict__)
            }
            return fn

        return decorator

    def format_doc(self):
        """ 格式化文档数据 """
        _formatted = {}
        for view_name, doc_map in self.apidoc.items():
            # 处理args
            args = {}
            temp = dict(**doc_map)
            if 'args' in doc_map:
                for location, fields in doc_map['args'].items():
                    args[location] = {}
                    for arg_name, arg_field in fields.items():
                        args[location][arg_name] = {
                            'type': arg_field.__class__.__name__,
                            'required': getattr(arg_field, 'required', False)
                        }
            temp['args'] = args
            # 处理注释
            doc_text = doc_map.get('doc', '').split('\n')
            temp['doc'] = list(
                filter(lambda t: len(t), [t.strip() for t in doc_text]))
            _formatted[view_name] = temp
        return _formatted

    def _add_error_info(self, error_code, api_name):
        """ 自动添加部分错误信息 """
        errors = self.apidoc[api_name]['errors']
        if error_code not in errors:
            errors[error_code] = self.handle_get_error_text(error_code)

    # custom handler
    def handle_unauthorization(self, msg=None):
        return abort(401)

    def handle_bad_arguments(self, msg=None):
        return abort(422)

    def handle_visit_limit(self, msg=None):
        return abort(403)

    def handle_banned_user(self, msg=None):
        return abort(403)

    def handle_get_error_text(self, code):
        return STATUS_TEXT.get(code, 'error')
Exemple #15
0
 def parse_querystring(self, req, name, field):
     if name == 'search':
         v = FlaskParser.parse_querystring(self, req, name, field)
     else:
         v = super().parse_querystring(req, name, field)
     return v
Exemple #16
0
# -*- coding: utf-8 -*-

from flask import (request, session, redirect, url_for, abort, render_template,
                   flash, Blueprint)

from database import (Entries)

from webargs.flaskparser import (
    FlaskParser, )

from webargs import fields
from settings import config

from async_tasks import add_log

args_parser = FlaskParser()
article_blueprint = Blueprint('article', __name__)


@article_blueprint.route('/')
def show_entries():
    entries = Entries.get(0, 0)
    return render_template('show_entries.html', entries=entries)


@article_blueprint.route('/add', methods=['POST'])
def add_entry():
    if not session.get('logged_in'):
        abort(401)
    Entries.add(title=request.form['title'], text=request.form['text'])
    add_log.delay("New entry {0} was successfully posted".\
Exemple #17
0
from werkzeug.datastructures import ImmutableMultiDict
import pytest

from webargs import Arg, Missing
from webargs.core import ValidationError
from webargs.flaskparser import FlaskParser, use_args, use_kwargs, abort

from .compat import text_type


class TestAppConfig:
    TESTING = True
    DEBUG = True


parser = FlaskParser()

hello_args = {
    'name': Arg(text_type, default='World'),
}


@pytest.fixture
def testapp():
    app = Flask(__name__)
    app.config.from_object(TestAppConfig)

    @app.route('/handleform', methods=['post'])
    def handleform():
        """View that just returns the jsonified args."""
        args = parser.parse(hello_args, targets=('form', ))
Exemple #18
0
 def _parse_data(self, schema, request, *args, **kwargs):
     return FlaskParser().parse(schema, request, locations=('json',),
                                *args, **kwargs)
Exemple #19
0
def search_results():
    form = SearchForm(request.args)

    parser = FlaskParser()
    search_kwargs = parser.parse(search_args, request)
    if search_kwargs.get("page_size") is None or search_kwargs.get("page_size") == "":
        search_kwargs["page_size"] = "25"
    if search_kwargs.get("page") is None or search_kwargs.get("page") == "":
        search_kwargs["page"] = "1"
    if (search_kwargs.get("page_number") is None or search_kwargs.get("page_number") == "") and search_kwargs.get(
        "page"
    ) is not None:
        search_kwargs["page_number"] = search_kwargs["page"]

    sp = SearchPublications(search_url)
    search_results_response, resp_status_code = sp.get_pubs_search_results(
        params=search_kwargs
    )  # go out to the pubs API and get the search results
    try:
        search_result_records = search_results_response["records"]
        record_count = search_results_response["recordCount"]
        pagination = Pagination(
            page=int(search_kwargs["page_number"]),
            total=record_count,
            per_page=int(search_kwargs["page_size"]),
            record_name="Search Results",
            bs_version=3,
        )
        search_service_down = None
        start_plus_size = int(search_results_response["pageRowStart"]) + int(search_results_response["pageSize"])
        if record_count < start_plus_size:
            record_max = record_count
        else:
            record_max = start_plus_size

        result_summary = {
            "record_count": record_count,
            "page_number": search_results_response["pageNumber"],
            "records_per_page": search_results_response["pageSize"],
            "record_min": (int(search_results_response["pageRowStart"]) + 1),
            "record_max": record_max,
        }
    except TypeError:
        search_result_records = None
        pagination = None
        search_service_down = "The backend services appear to be down with a {0} status.".format(resp_status_code)
        result_summary = {}
    if "mimetype" in request.args and request.args.get("mimetype") == "ris":
        content = render_template("pubswh/ris_output.ris", search_result_records=search_result_records)
        return Response(
            content,
            mimetype="application/x-research-info-systems",
            headers={"Content-Disposition": "attachment;filename=PubsWarehouseResults.ris"},
        )
    if request.args.get("map") == "True":
        for record in search_result_records:
            record = jsonify_geojson(record)

    return render_template(
        "pubswh/search_results.html",
        result_summary=result_summary,
        search_result_records=search_result_records,
        pagination=pagination,
        search_service_down=search_service_down,
        form=form,
        pub_url=pub_url,
    )
Exemple #20
0
class ArgumentsMixin:
    """Extend Blueprint to add arguments parsing feature"""

    ARGUMENTS_PARSER = FlaskParser()

    def arguments(self,
                  schema,
                  *,
                  location='json',
                  content_type=None,
                  required=True,
                  description=None,
                  example=None,
                  examples=None,
                  **kwargs):
        """Decorator specifying the schema used to deserialize parameters

        :param type|Schema schema: Marshmallow ``Schema`` class or instance
            used to deserialize and validate the argument.
        :param str location: Location of the argument.
        :param str content_type: Content type of the argument.
            Should only be used in conjunction with ``json``, ``form`` or
            ``files`` location.
            The default value depends on the location and is set in
            ``Blueprint.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING``.
            This is only used for documentation purpose.
        :param bool required: Whether argument is required (default: True).
        :param str description: Argument description.
        :param dict example: Parameter example.
        :param list examples: List of parameter examples.
        :param dict kwargs: Keyword arguments passed to the webargs
            :meth:`use_args <webargs.core.Parser.use_args>` decorator used
            internally.

        The `required` and `description` only affect `body` arguments
        (OpenAPI 2) or `requestBody` (OpenAPI 3), because the docs expose the
        whole schema. For other locations, the schema is turned into an array
        of parameters and the required/description value of each parameter item
        is taken from the corresponding field in the schema.

        The `example` and `examples` parameters are mutually exclusive and
        should only be used with OpenAPI 3 and when location is ``json``.

        See :doc:`Arguments <arguments>`.
        """
        # At this stage, put schema instance in doc dictionary. Il will be
        # replaced later on by $ref or json.
        parameters = {
            'in': location,
            'required': required,
            'schema': schema,
        }
        if content_type is not None:
            parameters['content_type'] = content_type
        if example is not None:
            parameters['example'] = example
        if examples is not None:
            parameters['examples'] = examples
        if description is not None:
            parameters['description'] = description

        def decorator(func):
            @wraps(func)
            def wrapper(*f_args, **f_kwargs):
                return func(*f_args, **f_kwargs)

            # Add parameter to parameters list in doc info in function object
            # The deepcopy avoids modifying the wrapped function doc
            wrapper._apidoc = deepcopy(getattr(wrapper, '_apidoc', {}))
            wrapper._apidoc.setdefault('parameters', []).append(parameters)

            # Call use_args (from webargs) to inject params in function
            return self.ARGUMENTS_PARSER.use_args(schema,
                                                  locations=[location],
                                                  **kwargs)(wrapper)

        return decorator
Exemple #21
0
import logging

from webargs import core
from webargs.flaskparser import FlaskParser
from marshmallow import Schema, fields, pre_load, post_load

from ._exceptions import UnprocessableEntity

parser = FlaskParser(('query', 'form', 'data'))
log = logging.getLogger(__name__)


@parser.location_handler('data')
def parse_data(request, name, field):
    return core.get_value(request.data, name, field)


class ValidatorMixin(object):
    @pre_load
    def log_input(self, data):  # pylint: disable=no-self-use
        log.debug("Input data: %r", data)

    @post_load
    def log_parsed(self, data):  # pylint: disable=no-self-use
        log.debug("Parsed data: %r", data)

    def handle_error(self, error, data):  # pylint: disable=no-self-use
        log.error("Unable to parse: %r", data)

        missing = []
        for field in sorted(error.messages):
Exemple #22
0
from webargs import core
from webargs.flaskparser import FlaskParser

parser = FlaskParser(('form', 'data'))


@parser.location_handler('data')
def parse_data(request, name, field):
    return core.get_value(request.data, name, field)
Exemple #23
0
class ArgumentsMixin:
    """Extend Blueprint to add arguments parsing feature"""

    ARGUMENTS_PARSER = FlaskParser()

    def arguments(self,
                  schema,
                  *,
                  location='json',
                  content_type=None,
                  required=True,
                  description=None,
                  example=None,
                  examples=None,
                  **kwargs):
        """Decorator specifying the schema used to deserialize parameters

        :param type|Schema schema: Marshmallow ``Schema`` class or instance
            used to deserialize and validate the argument.
        :param str location: Location of the argument.
        :param str content_type: Content type of the argument.
            Should only be used in conjunction with ``json``, ``form`` or
            ``files`` location.
            The default value depends on the location and is set in
            ``Blueprint.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING``.
            This is only used for documentation purpose.
        :param bool required: Whether argument is required (default: True).
        :param str description: Argument description.
        :param dict example: Parameter example.
        :param list examples: List of parameter examples.
        :param dict kwargs: Keyword arguments passed to the webargs
            :meth:`use_args <webargs.core.Parser.use_args>` decorator used
            internally.

        The `required` and `description` only affect `body` arguments
        (OpenAPI 2) or `requestBody` (OpenAPI 3), because the docs expose the
        whole schema. For other locations, the schema is turned into an array
        of parameters and the required/description value of each parameter item
        is taken from the corresponding field in the schema.

        The `example` and `examples` parameters are mutually exclusive and
        should only be used with OpenAPI 3 and when location is ``json``.

        See :doc:`Arguments <arguments>`.
        """
        # At this stage, put schema instance in doc dictionary. Il will be
        # replaced later on by $ref or json.
        parameters = {
            'in': location,
            'required': required,
            'schema': schema,
        }
        if content_type is not None:
            parameters['content_type'] = content_type
        if example is not None:
            parameters['example'] = example
        if examples is not None:
            parameters['examples'] = examples
        if description is not None:
            parameters['description'] = description

        error_status_code = kwargs.get(
            'error_status_code',
            self.ARGUMENTS_PARSER.DEFAULT_VALIDATION_STATUS)

        def decorator(func):
            @wraps(func)
            def wrapper(*f_args, **f_kwargs):
                return func(*f_args, **f_kwargs)

            # Add parameter to parameters list in doc info in function object
            # The deepcopy avoids modifying the wrapped function doc
            wrapper._apidoc = deepcopy(getattr(wrapper, '_apidoc', {}))
            docs = wrapper._apidoc.setdefault('arguments', {})
            docs.setdefault('parameters', []).append(parameters)
            docs.setdefault('responses',
                            {})[error_status_code] = http.HTTPStatus(
                                error_status_code).name

            # Call use_args (from webargs) to inject params in function
            return self.ARGUMENTS_PARSER.use_args(schema,
                                                  location=location,
                                                  **kwargs)(wrapper)

        return decorator

    def _prepare_arguments_doc(self, doc, doc_info, spec, **kwargs):
        # This callback should run first as it overrides existing parameters
        # in doc. Following callbacks should append to parameters list.
        operation = doc_info.get('arguments', {})

        parameters = [
            p for p in operation.get('parameters', [])
            if isinstance(p, abc.Mapping)
        ]

        # OAS 2
        if spec.openapi_version.major < 3:
            for param in parameters:
                if param['in'] in (self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING):
                    content_type = (
                        param.pop('content_type', None) or
                        self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING[param['in']]
                    )
                    if content_type != DEFAULT_REQUEST_BODY_CONTENT_TYPE:
                        operation['consumes'] = [
                            content_type,
                        ]
                    # body and formData are mutually exclusive
                    break
        # OAS 3
        else:
            for param in parameters:
                if param['in'] in (self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING):
                    request_body = {
                        x: param[x]
                        for x in ('description', 'required') if x in param
                    }
                    fields = {
                        x: param.pop(x)
                        for x in ('schema', 'example', 'examples')
                        if x in param
                    }
                    content_type = (
                        param.pop('content_type', None) or
                        self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING[param['in']]
                    )
                    request_body['content'] = {content_type: fields}
                    operation['requestBody'] = request_body
                    # There can be only one requestBody
                    operation['parameters'].remove(param)
                    if not operation['parameters']:
                        del operation['parameters']
                    break
        doc = deepupdate(doc, operation)
        return doc
Exemple #24
0
def parser():
    return FlaskParser()
Exemple #25
0
import json
from datetime import datetime
from webargs import fields
from webargs.flaskparser import use_args, FlaskParser

from . import main
from .. import socketio
from .config import ferm_active_sessions_path
from .model import PicoFermSession
from .session_parser import active_ferm_sessions

arg_parser = FlaskParser()

# Register: /API/PicoFerm/isRegistered?uid={uid}&token={token}
# Response: '#{0}#' where {0} : 1 = Registered, 0 = Not Registered
ferm_registered_args = {
    'uid':
    fields.Str(required=True),  # 12 character alpha-numeric serial number
    'token': fields.Str(required=True),  # 8 character alpha-numberic number
}


@main.route('/API/PicoFerm/isRegistered')
@use_args(ferm_registered_args, location='querystring')
def process_ferm_registered(args):
    return '#1#'


# Check Firmware: /API/PicoFerm/checkFirmware?uid={UID}&version={VERSION}
#           Response: '#{0}#' where {0} : 1 = Update Available, 0 = No Updates
check_ferm_firmware_args = {
Exemple #26
0
from flask import Blueprint, jsonify, abort, url_for, current_app, render_template, request, redirect
from flask.ext.login import login_required, current_user

from webargs import fields
from webargs.flaskparser import FlaskParser

from chess.chess import Chess
from .database import Game, User

from tests.factories import UserFactory  # TODO: remove

blueprint = Blueprint("chess", __name__, url_prefix='/chess/')
parser = FlaskParser(('query', 'json'))

move_args = {
    'start': fields.Str(required=True),
    'end': fields.Str(required=True),
}

game_args = {
    'color': fields.Str(required=False, missing=None),
}

create_user_args = {
    'password': fields.Str(required=True),
    'email': fields.Str(required=True),
    'name': fields.Str()
}


def get_requested_index(request):