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