Pydantic - Allow missing field - pydantic

I have a pydantic model. One of its fields must be supplied by user, however, the second one can be present but it is totally okay if it is missing.
In case of missing age, I don't want it to be present on pydantic model instance at all.
My Model:
from pydantic import BaseModel
class Employee(BaseModel):
name: str
age: Optional[int]
Problem:
e = Employee(name="Harwey Smith")
e.age # Has age parameter equal to None, even if not provided.
How do I setup my Employee pydantic model not to have age attribute at all if it is not provided on init? If I would try to access e.nonexistent attribute, AttributeError would be thrown. This is what I want to happen for e.age, if not provided.

In my opinion, your case is rather unusual and I would always prefer to have a field with a None value. I think it is simpler and receiving an AttributeError is something unexpected if we consider the type hint that any IDE would show for age. Having said that, what you want is possible if you use a #rootvalidator to remove the age field:
from typing import Any, Dict, Optional
from pydantic import BaseModel, root_validator
class Employee(BaseModel):
name: str
age: Optional[int]
#root_validator(skip_on_failure=True)
def _remove_age_if_none(cls, values: Dict[str, Any]) -> Dict[str, Any]:
if "age" in values and values["age"] is None:
del values["age"]
return values
e = Employee(name="Harwey Smith")
print(e.age)
Output:
AttributeError: 'Employee' object has no attribute 'age'
This approach is something I haven't seen in the Pydantic documentation, but I have seen some answers that show that a #rootvalidator can be used to delete a field. In my opinion this behavior is odd and I would not be surprised if it changed in future releases of Pydantic. Be careful.

Related

Is it possible to load only valid data to a pydantic model?

I'm trying to migrate input validation from marshmallow to pydantic for data validation as pydantic is used elsewhere in the project. I've got a bit stuck though, as the current code has the option to return only valid data extracted from the marshmallow ValidationError, like this:
try:
MyMarshmallowSchema().load(payload)
return payload
except ValidationError as vld_fail:
return vld_fail.valid_data
Is it possible to do something equivalent with pydantic?

How can I create a MappingModel using pydantic?

I am trying to use pydantic in order to create a JSON schema that would validate a generic mapping, something where I can have any key as string and each key can have any value assigned to it.
How can I do this?
So far I was successful in using BaseModel to create models for mappings where I know each key name and type, but in this case it should be any key as long is text (or follows a regex pattern for naming), without restrictions on the value assignment.
I'm not sure that I fully understand your case, but maybe a custom root type is what you need. Something like this:
from typing import Dict, Any
from pydantic import BaseModel
class Model(BaseModel):
__root__: Dict[str, Any]
def __iter__(self):
return iter(self.__root__)
def __getattr__(self, item):
return self.__root__[item]
m = Model.parse_obj({'key1': 'val1', 'key2': 'val2'})
assert m.key1 == "val1"

Why would I not get a printed list for some ORM Methods in SQLAlchemy?

So I'm just trying to make sense of the output of the SQLAlchemy ORM methods after creating a model, committing some entries and running queries. Most queries are fine...I'm getting back a list but for some it just returns an object (see below). I know this sounds obvious but is this normal behavior? I'm specifically referring to the filter_by query as you can see below...
#sample_app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI']='...'
db = SQLAlchemy(app)
class Person(db.Model):
__tablename__='persons'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), nullable=False)
def __repr__(self):
return f'<Person Id: {self.id}, name: {self.name}>'
db.create_all()
#Run some basic commands in interactive mode with model already populated
python3
from sample_app import db,Person
#add a bunch of persons
person1=Person(name='Amy')
person2=...
db.session.add(person1)
db.session.commit()
...
#Run queries
Person.query.all() #returns all persons as a list
Person.query.first() #returns first item in the list
Person.query.filter_by(name='Amy')
#returns <flask_sqlalchemy.Basequery object at 0xsadfjasdfsd>
So why am I not getting the same type of output for the third query for 'Amy'? is that normal behavior for the filter_by method?
Thanks
You didn’t execute the query in the last example. The all method brings back all object selected by the query, first is the first. You’ve specified a filter in the last example, but you didn’t execute a method which processes the query and returns a result [set].
If there are more than one Amy’s, you get all the matches with all() or the first with first(). If you had a filter which should yield a unique record, you could also use .one()

Odoo 10 get model name from record

I'm creating a log entry within a server action in Odoo 10. This is the server action Python code:
if record:
env['ir.logging'].create({
'type': 'server',
'name': 'test',
'level': record._name,
'path': 'test',
'line': 'test,
'func': '#',
'message': '#'
})
The line value for level of record._name gets me the name of the current model as a string, for example, product.product, or sale.order.
However, I don't think the _name attribute is not meant to be part of the public API. Does anyone know of a public way to retrieve the name of the model?
The model variable is also available in the server action, which returns product.product(). However, want product.product, without the parenthesis. I realize I could just use a string method to trim off the (), but I'm wondering if there is an intended way to get just the name of the model as a string.
Any ideas?
I don't see another way. _name is "private" and you could use __str__, which will give you the model name and the ids of the current RecordSet. The regex for odoo's model names is r'^[a-z0-9_.]+$', so i suggest, what you've already known: trim off everything at the end.

Django REST framework flat, read-write serializer

In Django REST framework, what is involved in creating a flat, read-write serializer representation? The docs refer to a 'flat representation' (end of the section http://django-rest-framework.org/api-guide/serializers.html#dealing-with-nested-objects) but don't offer examples or anything beyond a suggestion to use a RelatedField subclass.
For instance, how to provide a flat representation of the User and UserProfile relationship, below?
# Model
class UserProfile(models.Model):
user = models.OneToOneField(User)
favourite_number = models.IntegerField()
# Serializer
class UserProfileSerializer(serializers.ModelSerializer):
email = serialisers.EmailField(source='user.email')
class Meta:
model = UserProfile
fields = ['id', 'favourite_number', 'email',]
The above UserProfileSerializer doesn't allow writing to the email field, but I hope it expresses the intention sufficiently well. So, how should a 'flat' read-write serializer be constructed to allow a writable email attribute on the UserProfileSerializer? Is it at all possible to do this when subclassing ModelSerializer?
Thanks.
Looking at the Django REST framework (DRF) source I settled on the view that a DRF serializer is strongly tied to an accompanying Model for unserializing purposes. Field's source param make this less so for serializing purposes.
With that in mind, and viewing serializers as encapsulating validation and save behaviour (in addition to their (un)serializing behaviour) I used two serializers: one for each of the User and UserProfile models:
class UserSerializer(serializer.ModelSerializer):
class Meta:
model = User
fields = ['email',]
class UserProfileSerializer(serializer.ModelSerializer):
email = serializers.EmailField(source='user.email')
class Meta:
model = UserProfile
fields = ['id', 'favourite_number', 'email',]
The source param on the EmailField handles the serialization case adequately (e.g. when servicing GET requests). For unserializing (e.g. when serivicing PUT requests) it is necessary to do a little work in the view, combining the validation and save behaviour of the two serializers:
class UserProfileRetrieveUpdate(generics.GenericAPIView):
def get(self, request, *args, **kwargs):
# Only UserProfileSerializer is required to serialize data since
# email is populated by the 'source' param on EmailField.
serializer = UserProfileSerializer(
instance=request.user.get_profile())
return Response(serializer.data)
def put(self, request, *args, **kwargs):
# Both UserSerializer and UserProfileSerializer are required
# in order to validate and save data on their associated models.
user_profile_serializer = UserProfileSerializer(
instance=request.user.get_profile(),
data=request.DATA)
user_serializer = UserSerializer(
instance=request.user,
data=request.DATA)
if user_profile_serializer.is_valid() and user_serializer.is_valid():
user_profile_serializer.save()
user_serializer.save()
return Response(
user_profile_serializer.data, status=status.HTTP_200_OK)
# Combine errors from both serializers.
errors = dict()
errors.update(user_profile_serializer.errors)
errors.update(user_serializer.errors)
return Response(errors, status=status.HTTP_400_BAD_REQUEST)
First: better handling of nested writes is on it's way.
Second: The Serializer Relations docs say of both PrimaryKeyRelatedField and SlugRelatedField that "By default this field is read-write..." — so if your email field was unique (is it?) it might be you could use the SlugRelatedField and it would just work — I've not tried this yet (however).
Third: Instead I've used a plain Field subclass that uses the source="*" technique to accept the whole object. From there I manually pull the related field in to_native and return that — this is read-only. In order to write I've checked request.DATA in post_save and updated the related object there — This isn't automatic but it works.
So, Fourth: Looking at what you've already got, my approach (above) amounts to marking your email field as read-only and then implementing post_save to check for an email value and perform the update accordingly.
Although this does not strictly answer the question - I think it will solve your need. The issue may be more in the split of two models to represent one entity than an issue with DRF.
Since Django 1.5, you can make a custom user, if all you want is some method and extra fields but apart from that you are happy with the Django user, then all you need to do is:
class MyUser(AbstractBaseUser):
favourite_number = models.IntegerField()
and in settings: AUTH_USER_MODEL = 'myapp.myuser'
(And of course a db-migration, which could be made quite simple by using db_table option to point to your existing user table and just add the new columns there).
After that, you have the common case which DRF excels at.