def test_dict2schema_doesnt_add_to_class_registry(): old_n_entries = len( list( itertools.chain( [classes for _, classes in class_registry._registry.items()]))) argmap = {"id": fields.Field()} dict2schema(argmap) dict2schema(argmap) new_n_entries = len( list( itertools.chain( [classes for _, classes in class_registry._registry.items()]))) assert new_n_entries == old_n_entries
def test_delimited_list_load_list(web_request, parser): web_request.json = {"ids": [1, 2, 3]} schema_cls = dict2schema({"ids": fields.DelimitedList(fields.Int())}) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == [1, 2, 3]
def use_args( self, argmap, locations=None, as_kwargs=False, validate=None, error_status_code=None, error_headers=None ): """Creates a decorator funtion for socketio on-methods""" locations = locations or self.locations # Optimization: If argmap is passed as a dictionary, we only need # to generate a Schema once if isinstance(argmap, Mapping): argmap = dict2schema(argmap)() def decorator(func): def socket_wrapper(data, *args, **kwargs): # The first argument is either `self` or `request` parsed_args = self.parse( argmap, req=data, locations=locations, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) if as_kwargs: kwargs.update(parsed_args) return func(*args, **kwargs) return func(parsed_args, *args, **kwargs) return socket_wrapper return decorator
def test_delimited_list_custom_delimiter(web_request, parser): web_request.json = {"ids": "1|2|3"} schema_cls = dict2schema({"ids": fields.DelimitedList(fields.Int(), delimiter="|")}) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == [1, 2, 3]
def test_delimited_list_passed_invalid_type(web_request, parser): web_request.json = {"ids": 1} schema_cls = dict2schema({"ids": fields.DelimitedList(fields.Int())}) schema = schema_cls() with pytest.raises(ValidationError) as excinfo: parser.parse(schema, web_request) assert excinfo.value.messages == {"ids": ["Not a valid list."]}
def test_delimited_tuple_incorrect_arity(web_request, parser): web_request.json = {"ids": "1,2"} schema_cls = dict2schema( {"ids": fields.DelimitedTuple((fields.Int, fields.Int, fields.Int))}) schema = schema_cls() with pytest.raises(ValidationError): parser.parse(schema, web_request)
def test_dict2schema_with_nesting(): argmap = {"nest": fields.Nested({"foo": fields.Field()})} schema_cls = dict2schema(argmap) assert issubclass(schema_cls, Schema) schema = schema_cls() assert "nest" in schema.fields assert type(schema.fields["nest"]) is fields.Nested assert "foo" in schema.fields["nest"].schema.fields
def use_args(self, argmap, req=None, *, location=core.Parser.DEFAULT_LOCATION, as_kwargs=False, validate=None, error_status_code=None, error_headers=None): """Decorator that injects parsed arguments into a view callable. Supports the *Class-based View* pattern where `request` is saved as an instance attribute on a view class. :param dict argmap: Either a `marshmallow.Schema`, a `dict` of argname -> `marshmallow.fields.Field` pairs, or a callable which accepts a request and returns a `marshmallow.Schema`. :param req: The request object to parse. Pulled off of the view by default. :param str location: Where on the request to load values. :param bool as_kwargs: Whether to insert arguments as keyword arguments. :param callable validate: Validation function that receives the dictionary of parsed arguments. If the function returns ``False``, the parser will raise a :exc:`ValidationError`. :param int error_status_code: Status code passed to error handler functions when a `ValidationError` is raised. :param dict error_headers: Headers passed to error handler functions when a a `ValidationError` is raised. """ location = location or self.location # Optimization: If argmap is passed as a dictionary, we only need # to generate a Schema once if isinstance(argmap, Mapping): argmap = core.dict2schema(argmap, schema_class=self.schema_class)() def decorator(func): @functools.wraps(func) def wrapper(obj, *args, **kwargs): # The first argument is either `self` or `request` try: # get self.request request = req or obj.request except AttributeError: # first arg is request request = obj # NOTE: At this point, argmap may be a Schema, callable, or dict parsed_args = self.parse( argmap, req=request, location=location, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) args, kwargs = self._update_args_kwargs( args, kwargs, parsed_args, as_kwargs) return func(obj, *args, **kwargs) wrapper.__wrapped__ = func return wrapper return decorator
def _get_schema(self, argmap, req): if isinstance(argmap, BaseSchema): schema = argmap elif isinstance(argmap, type) and issubclass(argmap, BaseSchema): schema = argmap() elif callable(argmap): schema = argmap(req) else: schema = dict2schema(argmap, self.schema_class)() return schema
def test_delimited_list_default_delimiter(web_request, parser): web_request.json = {"ids": "1,2,3"} schema_cls = dict2schema({"ids": fields.DelimitedList(fields.Int())}) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == [1, 2, 3] dumped = schema.dump(parsed) data = dumped.data if MARSHMALLOW_VERSION_INFO[0] < 3 else dumped assert data["ids"] == [1, 2, 3]
def test_delimited_list_load_list_errors(web_request, parser): web_request.json = {"ids": [1, 2, 3]} schema_cls = dict2schema({"ids": fields.DelimitedList(fields.Int())}) schema = schema_cls() with pytest.raises(ValidationError) as excinfo: parser.parse(schema, web_request) exc = excinfo.value assert isinstance(exc, ValidationError) errors = exc.args[0] assert errors["ids"] == ["Not a valid delimited list."]
def test_delimited_tuple_custom_delimiter(web_request, parser): web_request.json = {"ids": "1|2"} schema_cls = dict2schema({ "ids": fields.DelimitedTuple((fields.Int, fields.Int), delimiter="|") }) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == (1, 2) data = schema.dump(parsed) assert data["ids"] == "1|2"
def use_args( self, argmap, req=None, locations=None, as_kwargs=False, validate=None, error_status_code=None, error_headers=None, ): locations = locations or self.locations request_obj = req # Optimization: If argmap is passed as a dictionary, we only need # to generate a Schema once if isinstance(argmap, Mapping): argmap = dict2schema(argmap)() def decorator(func): req_ = request_obj @functools.wraps(func) def wrapper(*args, **kwargs): req_obj = req_ if not req_obj: req_obj = self.get_request_from_view_args( func, args, kwargs) # NOTE: At this point, argmap may be a Schema, or a callable parsed_args = self.parse( argmap, req=req_obj, locations=locations, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) if as_kwargs: # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ONLY CHANGE FROM ORIGINAL kwargs.update(animalify(parsed_args, types='snake')) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! return func(*args, **kwargs) else: # Add parsed_args after other positional arguments new_args = args + (parsed_args, ) return func(*new_args, **kwargs) wrapper.__wrapped__ = func return wrapper return decorator
def annotations2schema( func: typing.Callable, type_mapping: typing.Optional[TypeMapping] = None ) -> Schema: type_mapping = type_mapping or DEFAULT_TYPE_MAPPING annotations = getattr(func, "__annotations__", {}) signature = inspect.signature(func) fields_dict = {} for name, annotation in annotations.items(): # Skip over request argument and return annotation if name == "return" or ( isinstance(annotation, type) and issubclass(annotation, Request) ): continue fields_dict[name] = _type2field(name, annotation, signature, type_mapping) return core.dict2schema(fields_dict)
def test_delimited_list_as_string_v2(web_request, parser): web_request.json = {"dates": "2018-11-01,2018-11-02"} schema_cls = dict2schema({ "dates": fields.DelimitedList(fields.DateTime(format="%Y-%m-%d"), as_string=True) }) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["dates"] == [ datetime.datetime(2018, 11, 1), datetime.datetime(2018, 11, 2), ] dumped = schema.dump(parsed) data = dumped.data if MARSHMALLOW_VERSION_INFO[0] < 3 else dumped assert data["dates"] == "2018-11-01,2018-11-02"
def test_delimited_tuple_default_delimiter(web_request, parser): """ Test load and dump from DelimitedTuple, including the use of a datetime type (similar to a DelimitedList test below) which confirms that we aren't relying on __str__, but are properly de/serializing the included fields """ web_request.json = {"ids": "1,2,2020-05-04"} schema_cls = dict2schema({ "ids": fields.DelimitedTuple( (fields.Int, fields.Int, fields.DateTime(format="%Y-%m-%d"))) }) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == (1, 2, datetime.datetime(2020, 5, 4)) data = schema.dump(parsed) assert data["ids"] == "1,2,2020-05-04"
def test_delimited_list_with_datetime(web_request, parser): """ Test that DelimitedList(DateTime(format=...)) correctly parses and dumps dates to and from strings -- indicates that we're doing proper serialization of values in dump() and not just relying on __str__ producing correct results """ web_request.json = {"dates": "2018-11-01,2018-11-02"} schema_cls = dict2schema( {"dates": fields.DelimitedList(fields.DateTime(format="%Y-%m-%d"))}) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["dates"] == [ datetime.datetime(2018, 11, 1), datetime.datetime(2018, 11, 2), ] dumped = schema.dump(parsed) data = dumped.data if MARSHMALLOW_VERSION_INFO[0] < 3 else dumped assert data["dates"] == "2018-11-01,2018-11-02"
def test_dict2schema(): data_key_kwargs = { "load_from" if (MARSHMALLOW_VERSION_INFO[0] < 3) else "data_key": "content-type" } argmap = { "id": fields.Int(required=True), "title": fields.Str(), "description": fields.Str(), "content_type": fields.Str(**data_key_kwargs), } schema_cls = dict2schema(argmap) assert issubclass(schema_cls, Schema) schema = schema_cls() for each in ["id", "title", "description", "content_type"]: assert each in schema.fields assert schema.fields["id"].required if MARSHMALLOW_VERSION_INFO[0] < 3: assert schema.opts.strict is True
def __init__(self, nested: typing.Union[typing.Dict, SchemaABC, type, str], *args, **kwargs): if isinstance(nested, dict): nested = dict2schema(nested) super().__init__(nested, *args, **kwargs) # type: ignore
def __init__(self, nested, *args, **kwargs): if isinstance(nested, dict): nested = dict2schema(nested) super(Nested, self).__init__(nested, *args, **kwargs)
def use_args( self, argmap: ArgMap, req: typing.Optional[Request] = None, locations: typing.Iterable = None, as_kwargs: bool = False, validate: Validate = None, error_status_code: typing.Optional[int] = None, error_headers: typing.Union[typing.Mapping[str, str], None] = None, ) -> typing.Callable[..., typing.Callable]: """Decorator that injects parsed arguments into a view function or method. Receives the same arguments as `webargs.core.Parser.use_args`. """ locations = locations or self.locations request_obj = req # Optimization: If argmap is passed as a dictionary, we only need # to generate a Schema once if isinstance(argmap, Mapping): argmap = core.dict2schema(argmap)() def decorator(func: typing.Callable) -> typing.Callable: req_ = request_obj if inspect.iscoroutinefunction(func): @functools.wraps(func) async def wrapper(*args, **kwargs): req_obj = req_ if not req_obj: req_obj = self.get_request_from_view_args( func, args, kwargs) # NOTE: At this point, argmap may be a Schema, callable, or dict parsed_args = await self.parse( argmap, req=req_obj, locations=locations, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) if as_kwargs: kwargs.update(parsed_args or {}) return await func(*args, **kwargs) else: # Add parsed_args after other positional arguments new_args = args + (parsed_args, ) return await func(*new_args, **kwargs) else: @functools.wraps(func) # type: ignore def wrapper(*args, **kwargs): req_obj = req_ if not req_obj: req_obj = self.get_request_from_view_args( func, args, kwargs) # NOTE: At this point, argmap may be a Schema, callable, or dict parsed_args = yield from self.parse( # type: ignore argmap, req=req_obj, locations=locations, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) if as_kwargs: kwargs.update(parsed_args) return func(*args, **kwargs) # noqa: B901 else: # Add parsed_args after other positional arguments new_args = args + (parsed_args, ) return func(*new_args, **kwargs) return wrapper return decorator
def test_load_json_called_by_parse_default(load_json, web_request): schema = dict2schema({"foo": fields.Field()})() load_json.return_value = {"foo": 1} p = Parser() p.parse(schema, web_request) load_json.assert_called_with(web_request, schema)