I am creating an API using marshmallow for the data validation.
the data is given to the schema in JSON:
data = request.get_json()
schema = ItemSchema()
evaluated = schema.load(data)
if evaluated.errors:
return {'message': evaluated.errors}, 400
The schema has field validation methods which are decorated with the #validates decorator:
#validates('name')
def validate_name(self, name):
existing_item = ItemModel.name_exists(name) #returns an object of type Item if the name exists. Names are unique
if existing_item and existing_item._id != data['_id']:
raise ValidationError('Item already exists.')
As in this example i would like to access the data dictionary which is passed via the load function. How can i access the data object inside the validation method of the schema?
Thanks for your help!
To answer your question, you can use a schema validator with the #validates_schema decorator. It has a pass_original parameter.
#validates_schema(pass_original=True)
def validate_name(self, data, input_data):
existing_item = ItemModel.name_exists(data['name'])
if existing_item and existing_item._id != input_data['_id']:
raise ValidationError('Item already exists.')
But frankly, I think your use case is wrong.
If it is an item creation (POST), just check whether the name already exists.
If it is an item modification (PUT), you know the ID from the request path. And you should be able to access it from your object.
Also, if I may suggest, you could use webargs (maintained by marshmallow maintainers) to parse request with marshmallow easily.
Related
I have a field on a schema that should be a specific schema based on a value in that field if it exists. To elaborate, the field should be fields.Dict() if it doesn't contain a version property. Otherwise, the schema should be retrieved from a map
def pricing_schema_serialization(base_object, parent_obj):
# Use dictionary access to support using this function to look up schema for both both serialization directions,
# and test data
if type(base_object) is not dict:
object_dict = base_object.__dict__
else:
object_dict = base_object
pricing_version = object_dict.get("version", None)
if pricing_version in version_to_schema:
return version_to_schema[pricing_version]() # Works fine
return fields.Dict(missing=dict, default=dict) # Produces error
class Product:
# pricing = fields.Dict(missing=dict, default=dict) # This worked
pricing = PolyField(
serialization_schema_selector=pricing_schema_serialization,
deserialization_schema_selector=pricing_schema_serialization,
required=False,
missing=dict,
default=dict,
)
This is the error produced:
{'pricing': ["Unable to use schema. Ensure there is a deserialization_schema_selector and that it returns a schema when the function is passed in {}. This is the class I got. Make sure it is a schema: <class 'marshmallow.fields.Dict'>"]}
I saw that marhmallow 3.x has a from_dict() method. Unfortunately, we're stuck on 2.x.
Things I tried:
The code above
Creating my own from_dict class method following the same implementation here. Same result
Problem: I get a ValidationError when trying to perform a .save() when appending a value to an EmbeddedDocumentListField because I am missing required fields that already exist on the document.
Note that at this point the User document has already been created as part of the signup process, so it already has an email and password in the DB.
My classes:
class User(gj.Document):
email = db.EmailField(required=True, unique=True)
password = db.StringField(required=True)
long_list_of_thing_1s = db.EmbeddedDocumentListField("Thing1")
long_list_of_thing_2s = db.EmbeddedDocumentListField("Thing2")
class Thing1(gj.EmbeddedDocument):
some_string = db.StringField()
class Thing2(gj.EmbeddedDocument):
some_string = db.StringField()
Trying to append a new EmbeddedDocument to the EmbeddedDocumentListField in my User class in the Thing2 Resource endpoint:
class Thing2(Resource):
def post(self):
try:
body = request.get_json()
user_id = body["user_id"]
user = UserModel.objects.only("long_list_of_thing_2s").get(id=user_id)
some_string = body["some_string"]
new_thing_2 = Thing2Model()
new_thing_2.some_string = some_string
user.long_list_of_thing_2s.append(new_thing_2)
user.save()
return 201
except Exception as exception:
raise InternalServerError
On hitting this endpoint I get the following error on the user.save()
mongoengine.errors.ValidationError: ValidationError (User:603e39e7097f3e9a6829f422) (Field is required: ['email', 'password'])
I think this is because of the .only("long_list_of_thing_2s")
But I am specifically using UserModel.objects.only("long_list_of_thing_2s") because I don't want to be inefficient in bringing the entire UserModel into memory when I only want to append something the long_list_of_thing_2s
Is there a different way I should be going about this? I am relatively new to Flask and Mongoengine so I am not sure what all the best practices are when going about this process.
You are correct, this is due to the .only and is a known "bug" in MongoEngine.
Unless your Model is really large, using .only() will not make a big difference so I'd recommend to use it only if you observe performance issues.
If you do have to keep the .only() for whatever reason, you should be able to make use of the push atomic operator. An advantage of using the push operator is that in case of race conditions (concurrent requests), it will gracefully deal with the different updates, this is not the case with regular .save() which will overwrite the list.
I am using flask-restful this is
My class I want to insert
class OrderHistoryResource(Resource):
model = OrderHistoryModel
schema = OrderHistorySchema
order = OrderModel
product = ProductModel
def post(self):
value = req.get_json()
data = cls.schema(many=True).load(value)
data.insert()
In my model
def insert(self):
db.session.add(self)
db.session.commit()
schema
from config.ma import ma
from model.orderhistory import OrderHistoryModel
class OrderHistorySchema(ma.ModelSchema):
class Meta:
model = OrderHistoryModel
include_fk = True
Example Data I want to insert
[
{
"quantity":99,
"flaskSaleStatus":true,
"orderId":"ORDER_64a79028d1704406b6bb83b84ad8c02a_1568776516",
"proId":"PROD_9_1568779885_64a79028d1704406b6bb83b84ad8c02a"
},
{
"quantity":89,
"flaskSaleStatus":true,
"orderId":"ORDER_64a79028d1704406b6bb83b84ad8c02a_1568776516",
"proId":"PROD_9_1568779885_64a79028d1704406b6bb83b84ad8c02a"
}
]
this is what i got after insert method has started
TypeError: insert() takes exactly 2 arguments (0 given)
or there is another way to do this action?
Edited - released marshmallow-sqlalchemy loads directly to instance
You need to loop through the OrderModel instances in your list.
You can then use add_all to add the OrderModel objects to the session, then bulk update - see the docs
Should be something like:
db.session.add_all(data)
db.session.commit()
See this post for brief discussion on why add_all is best when you have complex ORM relationships.
Also - not sure you need to have all your models/schemas as class variables, it's fine to have them imported (or just present in the same file, as long as they're declared before the resource class).
You are calling insert on list cause data is list of model OrderHistoryModel instances.
Also post method doesn't need to be classmethod and you probably had an error there as well.
Since data is list of model instances you can use db.session.add_all method to add them to session in bulk.
def post(self):
value = req.get_json()
data = self.schema(many=True).load(value)
db.session.add_all(data)
db.session.commit()
When I use like below
data , error = schema.load( json_data )
data object contains only values not keys but json_data is a valid dict.
and if I just use
MyModel = _mymodel(**json_data)
it works. But if I use below inside of my schema
#post_load
def create_model(self, data):
return MyModel(**data)
I get error
AttributeError: 'MyModel' object has no attribute 'get'
Anyone has any idea? Why "data" contains only value and does not return dictionary like the examples stated?
Thanks
According to documentation here there are two methods similar to each others.
load(data, many=None, partial=None)
Deserialize a data structure to an object defined by this Schema’s fields and make_object().
Another is
loads(json_data, many=None, *args, **kwargs)
Same as load(), except it takes a JSON string as input.
So you need to use loads() instead of load() if you have json data.
I want to create a viewset/apiview with a path like this: list/<slug:entry>/ that once I provide the entry it will check if that entry exists in the database.
*Note: on list/ I have a path to a ViewSet. I wonder if I could change the id with the specific field that I want to check, so I could see if the entry exists or not, but I want to keep the id as it is, so
I tried:
class CheckCouponAPIView(APIView):
def get(self, request, format=None):
try:
Coupon.objects.get(coupon=self.kwargs.get('coupon'))
except Coupon.DoesNotExist:
return Response(data={'message': False})
else:
return Response(data={'message': True})
But I got an error: get() got an unexpected keyword argument 'coupon'.
Here's the path: path('check/<slug:coupon>/', CheckCouponAPIView.as_view()),
Is there any good practice that I could apply in my situation?
What about trying something like this,
class CheckCouponAPIView(viewsets.ModelViewSet):
# other fields
lookup_field = 'slug'
From the official DRF Doc,
lookup_field - The model field that should be used to for performing
object lookup of individual model instances. Defaults to pk