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)
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)
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
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
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
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
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')
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
# -*- 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".\
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', ))
def _parse_data(self, schema, request, *args, **kwargs): return FlaskParser().parse(schema, request, locations=('json',), *args, **kwargs)
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, )
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
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):
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)
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
def parser(): return FlaskParser()
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 = {
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):