flask - something more strict than #api.expect for input data? - python

In my flask-restplus API I'd like not only to check that input data, like in the following example
resource_fields = api.model('Resource', {
'name': fields.String(default = 'string: name', required = True),
'state': fields.String(default = 'string: state'),
})
#api.route('/my-resource/<id>')
class MyResource(Resource):
#api.expect(resource_fields, validate=True)
def post(self):
...
must have 'name' field and may have 'state' field, but also to check that there are no other fields (and to raise an error if this happens).
Is there another decorator for this? Can I check the correctness of the input data by a custom function?

Instead of using a dictionary for your fields, try using a RequestParser (flask-restplus accepts both as documented here. That way, you can call parser.parse_args(strict=True) which will throw a 400 Bad Request exception if any unknown fields are present in your input data.
my_resource_parser = api.parser()
my_resource_parser.add_argument('name', type=str, default='string: name', required=True)
my_resource_parser.add_argument('state', type=str, default='string: state')
#api.route('/my-resource/<id>')
class MyResource(Resource):
def post(self):
args = my_resource_parser.parse_args(strict=True)
...
For more guidance on how to use the request_parser with your resource, check out the ToDo example app in the flask-restplus repo.

Here is another answer to complete the one from #shiv. The following code snippet allows you to have your payload documented in the Swagger doc generated by Flask Restplus. Taken from the documentation about the expect decorator:
my_resource_parser = api.parser()
my_resource_parser.add_argument('name', type=str, default='string: name', required=True)
my_resource_parser.add_argument('state', type=str, default='string: state')
#api.route('/my-resource/<id>', endpoint='with-parser')
class MyResource(Resource):
#api.expect(my_resource_parser)
def post(self):
args = my_resource_parser.parse_args(strict=True)
...

For those that want to continue using the api.model rather than request parser, it's possible to iterate over your input (assuming list) versus the model. The model behaves like a dictionary.
from flask_restplus import abort
def check_exact(response_list, model):
for response_dict in response_list:
for key in response_dict:
if key not in model:
abort(400, "Non-specified fields added", field=key)
...
#ns.expect(my_model, validate=True)
def post(self, token):
"""Add new set of responses
"""
check_exact(api.payload['responses'], my_model)
...

Related

Simple request parsing without reqparse.RequestParser()

flask_restful.reqparse has been deprecated (https://flask-restful.readthedocs.io/en/latest/reqparse.html):
The whole request parser part of Flask-RESTful is slated for removal and will be replaced by documentation on how to integrate with other packages that do the input/output stuff better (such as marshmallow). This means that it will be maintained until 2.0 but consider it deprecated. Don’t worry, if you have code using that now and wish to continue doing so, it’s not going to go away any time too soon.
I've looked briefly at Marshmallow and still a bit confused about how to use it if I wanted to replace reqparse.RequestParser(). What would we write instead of something like the following:
from flask import Flask, request, Response
from flask_restful import reqparse
#app.route('/', methods=['GET'])
def my_api() -> Response:
parser = reqparse.RequestParser()
parser.add_argument('username', type=str, required=True)
args = parser.parse_args()
return {'message': 'cool'}, 200
(after half an hour of reading some more documentation…)
RequestParser looks at the MultiDict request.values by default (apparently query parameters, then form body parameters according to https://stackoverflow.com/a/16664376/5139284). So then we just need to validate the data in request.values somehow.
Here's a snippet of some relevant code from Marshmallow. It seems a good deal more involved than reqparse: first you create a schema class, then instantiate it, then have it load the request JSON. I'd rather not have to write a separate class for each API endpoint. Is there something more lightweight similar to reqparse, where you can write all the types of the argument validation information within the function defining your endpoint?
from flask import Flask, request, Response
from flask_restful import reqparse
from marshmallow import (
Schema,
fields,
validate,
pre_load,
post_dump,
post_load,
ValidationError,
)
class UserSchema(Schema):
id = fields.Int(dump_only=True)
email = fields.Str(
required=True, validate=validate.Email(error="Not a valid email address")
)
password = fields.Str(
required=True, validate=[validate.Length(min=6, max=36)], load_only=True
)
joined_on = fields.DateTime(dump_only=True)
user_schema = UserSchema()
#app.route("/register", methods=["POST"])
def register():
json_input = request.get_json()
try:
data = user_schema.load(json_input)
except ValidationError as err:
return {"errors": err.messages}, 422
# etc.
If your endpoints share any commonalities in schema, you can use fields.Nested() to nest definitions within each Marshmallow class, which may save on code writing for each endpoint. Docs are here.
For example, for operations that update a resource called 'User', you would likely need a standardised subset of user information to conduct the operation, such as user_id, user_login_status, user_authorisation_level etc. These can be created once and nested in new classes for more specific user operations, for example updating a user's account:
class UserData(Schema):
user_id = fields.Int(required=True)
user_login_status = fields.Boolean(required=True)
user_authentication_level = fields.Int(required=True)
# etc ....
class UserAccountUpdate(Schema):
created_date = fields.DateTime(required=True)
user_data = fields.Nested(UserData)
# account update fields...

Is it possible to access non db key from request in Django signal?

I'm doing an api request and I'm sending a non-db key with the request.
In Django's pre-signal I want to use that key to make a decision.
Is it possible to do it? If yes how can I do it?
Example Scenario:
# POST request body from postman
{
"field1": 10,
"custom_field": True
}
# test model
class TestModel(models.Model):
field1 = model.IntegerField(default=0)
field2 = model.CharField(default="test")
# django pre save signal
pre_save.connect(test_function, sender=TestModel)
# test function in some helper
def test_function(sender, instance, raw, using,
update_fields, **extra_fields):
# here I'm using the custom field which is not in model
if instance.custom_field:
instance.field2 = "hello"
else:
instance.field2 = "hi"
I'm getting an error i.e. AttributeError: 'ModelTest' object has no attribute 'custom_field'
I hope you understand.
Thanks in advance.
You can pass arguments in .save() method.
Something like this: product.save(update_fields=['name'])
Note: It isn't necessary that you pass model fields only.
Reference: https://docs.djangoproject.com/en/2.2/ref/models/instances/#specifying-which-fields-to-save
You do the magic with the save method like below
class TestModel(models.Model):
field1 = model.IntegerField(default=0)
field2 = model.CharField(default="test")
def save(self, *args, **kwargs):
self.custom_field = kwargs.pop('custom_field', None)
super().save(*args, **kwargs)

Renaming DRF serializer fields

I'm using DRF serializers to validate incoming data that I retrieve from a JSON API. I'm trying to rename some awkwardly named fields from the response, making it easier to use the serializer.data further on in my code.
Data received from the API looks like this:
{"FunnyNamedField": true, "AnotherWeirdField": false}
And handling code:
resp = requests.get([...])
resp.raise_for_status()
ser = MyFunnyDataSerializer(data=resp.json())
if ser.is_valid():
do_domething_with(ser.data)
I would like the serializer to translate the incoming field names to something more consise. ser.data could look like: {'funny': True, 'weird': False}.
What I tried but doesn't work as I hoped:
class MyFunnyDataSerializer(serializers.Serializer):
funny = serializers.Booleanfield(source='FunnyNamedField')
Is there any way to achieve this without reverting to a SerializerMethodField?
You can override BaseSerializer to achieve this:
from rest_framework import serializers
class CustomSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
<datas>
}
You can do some specific modifications on instance serialization with custom methods.
Another solution could be to write your own validator for one field: Field Validator Method.
So in this documentation example you could modify value before return it.
from rest_framework import serializers
class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()
def validate_title(self, value):
"""
Check that the blog post is about Django.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
if value == "something":
value = "something_else"
return value
Hope it helps.

mutate() got multiple values for argument 'name'

I want to query createThemes to my graphql.
My graphql query is:
mutation{
createTheme(name: "qwe"){
theme{
id
}
}
}
So it errors: mutate() got multiple values for argument 'name' Can you solve and explain why its printing such error.
My code below:
from models import Theme as ThemeModel, Topic as TopicModel, Article as ArticleModel
...
class CreateTheme(graphene.Mutation):
class Arguments:
id = graphene.Int()
name = graphene.String()
theme = graphene.Field(lambda: Theme)
def mutate(self, name):
theme = ThemeModel(name = name)
theme.insert()
return CreateTheme(theme = theme)
...
class Mutation(graphene.ObjectType):
create_theme = CreateTheme.Field()
...
schema = graphene.Schema(query=Query, mutation = Mutation)
[Solved]
I changed
def mutate(self, name):
to this:
def mutate(self, info, name):
It's missing info parameter.
Info parameter provides two things:
reference to meta information about the execution of the current GraphQL Query (fields, schema, parsed query, etc.)
access to per-request context which can be used to store user authentication, data loader instances or anything else useful for resolving the query.
Source: https://docs.graphene-python.org/en/latest/types/objecttypes/

How to use Django REST Serializers?

So, after reading the Django REST Framework document, and a bunch of tutorials, I am still having trouble understanding how to use the Django serializers to convert incoming POST (JSON) data into a Python object (sorry, I'm new).
Given that I am posting a JSON string to, say, api/foo/bar, how do I write its serializer?
Example JSON:
{ 'name': 'Mr. Foo', address:'Bar Street' }
My controller, Foo contains a bar method as follows:
#detail_route(
methods=['post']
)
def bar(self, request, uuid=None):
serializer = MySampleSerializer(data=request.DATA)
something.clone(serializer.object)
return Response(status=status.HTTP_201_CREATED)
Can somebody explain to me what should my serializer look like? And how do I access the serialized data from the serializer?
As you do not want to use a model, you have to create the serializer from scratch. Something like this should maybe work:
class MySerializer(serializers.Serializer):
name = serializers.CharField(max_length = 100)
adress = serializers.CharField(max_length = 100)
And then you could use it in a request like this:
def bar(self, request, uuid=None):
data = JSONParser().parse(request)
serializer = MySerializer(data = data)
return Response(status=status.HTTP_201_CREATED)
Note however, as you have not created an Django model, you will not be able to save the serialized data (and thus nothing will be saved in the database)
Basically, you pass in the JSON data to the serializer, and then access the data field which will return an ordered dictionary.
def bar(self, request, uuid=None):
serializer = MySampleSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
my_object = serializer.data # Grab the dict of values
To define a serializer:
class MySampleSerializer(serializers.Serializer):
name = serializers.CharField(max_length=30)
address = serializers.CharField(max_length=30)
You do not have to use the ModelSerializer:
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
and access:
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
by the way, all the above is from the DRF website

Categories