How to validate request body in FastAPI? - python

I understand that if the incoming request body misses certain required keys, FastAPI will automatically raise 422 unserviceable entity error. However, is there a way to check the incoming request body by myself in the code and raise a 400 bad request if if misses required names?
For example, say I have this model and schema:
class Student(Base):
__tablename__ = "student"
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True, nullable=False)
email = Column(String(100), unique=True, nullable=False)
gpa = Column(Float, unique=False, nullable=False)
class StudentBase(BaseModel):
name: str
email: str
gpa: float
The POST endpoint to create a new row is:
#app.post("/student", dependencies=[Depends(check_request_header)],
response_model=schemas.Student, status_code=200)
def create_student(student: schemas.StudentCreate, db: Session = Depends(get_db)):
db_student = crud.get_student(db, student=student)
if db_student:
raise HTTPException(status_code=400, detail="This student has already been created.")
return crud.create_student(db=db, student=student)
The expected request body should be something like this:
{
"name": "johndoe",
"email": "johndoe#gmail.com",
"gpa": 5.0
}
is there a way to check the request body for the above endpoint?

This is normally handled by using pydantic to validate the schema before doing anything to the database at the ORM level. I highly recommend you use the FASTApi project generator and look at how it plugs together there: it's (currently) the easiest way to see the fastapi-> pydantic -> [orm] -> db model as FASTApi's author envisgaes it.
If you're not using an ORM, nothing stops you building an 'ORM lite' where the post data is parsed into a pydantic object (with .from_dict) and then you manually run the right queries. This error will propagate up to the endpoint function, where you can catch it and return your error as you want to.
Note that you can also do the validation yourself, any way you choose.
In general you raise an HTTPException if you need to signal that it failed.

Related

Stripp database response to fit in schema in fastapi [duplicate]

This question already has answers here:
FastAPI - GET request results in typeerror (value is not a valid dict)
(2 answers)
Closed 10 months ago.
I started to build a wepapp with fastapi and Vue. I started at the backend and tried to get some data from a database. As ORM I use SQLAlchemy. What I am tring to achive is to strip down the DB response to some specific columns.
I am fairly new to the world of webapplications, I will also appreciate some good resources. I find it kind of hard to get started in this topic. Despite there being alt of Tutorials, they just cover how to start a basic site and leave the rest to the docs. But I you are not used to the terminology, its quite easy to get lost there.
Anyway, I have this test setup:
My model for the db is
class System(Base):
__tablename__ = 'system'
id = Column(BIGINT(20), primary_key=True)
name = Column(String(200), nullable=False)
type = Column(String(200), nullable=False)
installed_power = Column(Float(asdecimal=True), nullable=False)
date_of_installation = Column(
DateTime, nullable=False, server_default=text("current_timestamp()"))
last_changed = Column(DateTime, nullable=False, server_default=text(
"current_timestamp() ON UPDATE current_timestamp()"))
site_id = Column(ForeignKey('site.id'), nullable=False, index=True)
site = relationship('Site')
and my schema is
class System(BaseModel):
id: int
name: str
type: str
installed_power: int
In my main.py I am doing this
#app.get("/system", response_model=schemas.System)
def get_systems(db: Session = Depends(get_db)):
query = crud.get_system(db)
return query.all()
This dose not work. The error says pydantic.error_wrappers.ValidationError: 1 validation error for System response value is not a valid dict (type=type_error.dict)
If I add all db columns to the schema it works obviously
I also tried something like this, but this did not work either.
#app.get("/system", response_model=schemas.System)
def get_systems(db: Session = Depends(get_db)):
query = crud.get_system(db)
return query.all()
res = []
for row in query:
system = schemas.System()
system.id = row.id
system.name = row.name
system.type = row.type
system.installed_power = row.installed_power
res.append(system)
return res
WITHOUT orm_mode
In your first example, your response_model expects a schema (System), but you send it query.all() which returns a list containing classes from the model you get. (SQLAlchemy does not return a dictionary, which is what pydantic expects by default).
So, at this point, you have to make a choice, either your endpoint must return ONE object, in which case you should not use query.all() but something like query.one_or_none().
Or you want to return a list of objects and your response_model should be:
from typing import List
#app.get("/system", response_model=List[schemas.System])
All that remains is to format your data so that it corresponds to your schema.
for version with only one data:
#app.get("/system", response_model=schemas.System)
def get_systems(db: Session = Depends(get_db)):
query = crud.get_system(db).one_or_none()
return schemas.System(id=query.id,name= query.name, type=query.type, installed_power=query.installed_power)
for version with multiple datas:
from typing import List
def make_response_systems(query):
result = []
for data in query:
result.append(schemas.System(id=query.id,name= query.name, type=query.type, installed_power=query.installed_power))
return result
#app.get("/system", response_model=List[schemas.System])
def get_systems(db: Session = Depends(get_db)):
query = crud.get_system(db).all()
return make_response_systems(query)
There are more aesthetic ways to do this. But I think the example above is a good way to understand how it works.
For the rest, you can look at the orm_mode of pydantics models, which you can find in the FastAPI documentation.
To learn, the FastAPI documentation is very complete and easy to access.
WITH orm_mode
Pydantic's orm_mode will tell the Pydantic model to read the data even if it is not a dict, but an ORM model (or any other arbitrary object with attributes).
class System(BaseModel):
id: int
name: str
type: str
installed_power: int
class Config:
orm_mode = True
#app.get("/system", response_model=schemas.System)
def get_systems(db: Session = Depends(get_db)):
query = crud.get_system(db).one_or_none()
return query
Documentation fastapi orm_mode : https://fastapi.tiangolo.com/tutorial/sql-databases/?h=orm_mode#use-pydantics-orm_mode
As per pydantic docs
ORM Mode (aka Arbitrary Class Instances)🔗
Pydantic models can be created from arbitrary class instances to support models that map to ORM objects.
To do this:
The Config property orm_mode must be set to True.
The special constructor from_orm must be used to create the model instance.
we need to add orm_mode to the schema config.
class System(BaseModel):
id: int
name: str
type: str
installed_power: int
class Config:
orm_mode = True
Reference: https://pydantic-docs.helpmanual.io/usage/models/#orm-mode-aka-arbitrary-class-instances

falcon-autocrud: how to handle unique rows?

I want to create a simple app with Falcon that is able to handle small sqlite database with hostname: ip records. I want to be able to replace rows in sqlite, so I decide that hostname is unique field. I have a model.py:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine, Column, Integer, String
Base = declarative_base()
DB_URI = 'sqlite:///clients.db'
class Client(Base):
__tablename__ = 'clients'
id = Column(Integer, primary_key=True)
hostname = Column(String(50), unique=True)
ip = Column(String(50))
My simple resources.py:
from falcon_autocrud.resource import CollectionResource, SingleResource
from models import *
class ClientCollectionResource(CollectionResource):
model = Client
methods = ['GET', 'POST']
When I make a POST-request with updated information about hostname:ip i get an Unique constraint violated error:
req = requests.post('http://localhost:8000/clients',
headers={'Content-Type': 'application/json'},
data=json.dumps({'hostname': 'laptop1', 'ip': '192.168.0.33'}));
req.content
>> b'{"title": "Conflict", "description": "Unique constraint violated"}'
Is there any way to replace existing records using sqlalchemy? Or maybe I was wrong choosing sqlite for these purposes?
When building a REST-ful API you should not use POST to update existing resources, POST to a resource should only ever create new resources. falcon-autocrud is doing the right thing here.
Instead, use PUT on the individual resource (the SingleResource resource registered for .../clients/<identifier>) to alter existing resources.
If you use hostname in your SingleResource definition then falcon-autocrud should automatically use that column as the identifier (assuming that your SingleResource subclass is called ClientResource):
app.add_route('/clients/{hostname}', ClientResource(db_engine))
at which point you can PUT the new ip value directly with:
requests.put('http://localhost:8000/clients/laptop1', json={'ip': '192.168.0.33'})
(Note that requests supports JSON requests directly; the json= keyword argument is encoded to JSON for you, and the Content-Type header is set for you automatically when you use it).
You may want to limit what fields are returned for your Client objects. With a unique hostname you wouldn't want to confuse clients by also sending the primary key column. I'd limit the response fields by setting the response_fields attribute on your resource classes:
class ClientCollectionResource(CollectionResource):
model = Client
response_fields = ['hostname', 'ip']
methods = ['GET', 'POST']
class ClientResource(SingleResource):
model = Client
response_fields = ['hostname', 'ip']
I see that falcon-autocrud doesn't yet support PATCH requests on the collection that alter existing resources (only "op": "add" is supported), otherwise that'd be another route to alter existing entries too.

How to store a value as a global variable that is specific to the user?

I am currently working on a small flask app that will be connecting to an api and processing data pulled from that api.
Users are able to login to my flask app and then also define their credentials to interact with the api. Any given user may have one or more API credentials associated with their user.
I've created db models to store user and Api credentials in the database as follows.
I'm using the flask-login module which has the "current_user" object which provides me with the User model of the user that is currently logged in across my entire app.
Models:
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(128), index=True, unique=True)
firstname = db.Column(db.String(55))
lastname = db.Column(db.String(55))
password = db.Column(db.String(128))
creds = db.relationship('ApiCredential', backref='owner', lazy='dynamic')
class ApiCredential(db.Model):
__tablename__ = 'api_credentials'
id = db.Column(db.Integer, primary_key=True)
site = db.Column(db.String(140))
user = db.Column(db.String(140))
username = db.Column(db.String(100), index=True, unique=True)
password = db.Column(db.String(55))
base = db.Column(db.String(100))
owner_id = db.Column(db.Integer, db.ForeignKey('users.id'))
active = db.Column(db.Boolean)
I would like to know how to create a similar "global variable" for my API credentials that is specific only to the logged in user and not to all users of the application
NOTE*** It seems as though "current_user" is something called a local proxy which i am not at all familiar with and cannot seem to find any decent documentation or explanation of what it is and how to use it for my needs.
You're in for a fun ride, at the end of which you might choose to do something less magic.
First, it helps to understand how current_user works. The source (as of this moment) is here. It's a werkzeug.local.LocalProxy, which wraps a lambda that calls flask_login.utils._get_user.
LocalProxy is pretty cool, and understanding it is a great way to level-up on Python, but flask-login uses a thin slice of it. The source (as of this moment) is here. It looks scary, but if you trace the code, it merely invokes the local arg (the lambda from flask-login).
That gets us back to _get_user (here, as of this moment), which loads a user if there isn't one (in the top of the current request context), and then returns the user from the top of the request context.
You could follow that pattern, and write a package that exports current_credentials. You'd follow the same pattern, using a werkzeug.local.LocalProxy to wrap a lambda that invoked, say, _get_credentials. The trick would be to import current_user from flask-login, using it with _get_credentials to get to user with which to construct the query to join to your ApiCredentials table.
Or you could take a simple approach, and write utility method for your views to use, which would use current_user to get the user and then do the join to get API credentials for that user.
One method could be to create a new route in your flask app, when the user get requests the page then you can check which user it is, once you know which user it is, you can query using your api credentials model and filter by current_user.id.
creds = ApiCredentials.query.filter_by(owner_id=current_user.id).first_or_404()
Then you can do as you please with the information stored in your API Credentials table.
I don't see why you would want to replicate the user loader functionality. This is the function you will have included at some point when you set up Flask login. Very short function that returns the current user object from the user model.
You could display or return your api keys. Display them on a page as HTML or for more autonomous jobs, you could use jsonify to return the keys as a json string.
I'm sorry this doesn't directly answer your question, but I hope my suggestion might lead you to a slightly less complex answer and you can continue developing your web app. Perhaps it would be worth revisiting at a later date.
Note: this is off the top of my head. The code line I provided might need to be as follows
creds = ApiCredentials.query.filter_by(owner_id==current_user.id).first()
Furthermore, you may not want to use .first() if they have multiple api credentials stored in the table.
In which case this would be more suitable:
creds = ApiCredentials.query.filter_by(owner_id==current_user.id).all()

Jsonify flask-sqlalchemy many-to-one relationship in flask

I was trying to make rest apis with flask-restful where i am using flask-sqlalchemy as the ORM.Here is my model classes.
class Post(db.Model):
__tablename__ = 'post'
postid = db.Column(db.Integer,primary_key=True)
post = db.Column(db.String(64))
userid = db.Column(db.Integer,db.ForeignKey('user.userid'))
#serialize property used for serializing this class to JSON
#property
def serialize(self):
return {
'postid': self.postid,
'post': self.post,
'author':self.author
}
and
class User(db.Model):
userid = db.Column(db.Integer,primary_key=True)
username = db.Column(db.String(30))
email = db.Column(db.String(20))
posts = db.relationship('Post',backref="author",lazy='dynamic')
#serialize property used for serializing this class to JSON
#property
def serialize(self):
return {
'username': self.username,
'email': self.email
}
And the database is populated.now i am trying to make the json out of this
class PostList(Resource):
def get(self):
posts = DL_models.Post.query.all()
posts = [post.serialize for post in posts]
return { 'posts': posts }
api.add_resource(PostList, '/twitter/api/v1.0/posts', endpoint = 'posts')
This works perfect when i change serialize method in Post to
#property
def serialize(self):
return {
'postid': self.postid,
'post': self.post,
'author':self.postid
}
This returns expected json output but when i am changing to 'author':self.author i am getting a error
TypeError: <app.DL_models.User object at 0x7f263f3adc10> is not JSON serializable
I understand that i have to call serialize on those nested objects as well but i could not figure out how to do it.
Or please share your experience to encode relationships in sqlalchemy.
Since you're already using Flask-Restful, have you considered using their built in data marshaling solution?
However, for marshaling complex data structures, I find that Marshmallow does the task about a thousand times better, even making nesting serializers within others easy. There's also a Flask extension designed to inspect endpoints and output URLs.
This is a punt but have you tried the below?
'author': self.author.username
From the error message I'm guessing it's getting to User but doesn't know what you want from it.

Serializing SQLAlchemy models for a REST API while respecting access control?

Currently, the way our, as well as most web frameworks', serialization works is there's some type of method invocation which dumps the model into some type of format. In our case, we have a to_dict() method on every model that constructs and returns a key-value dictionary with the key being the field name and the value being the instance variable.
All throughout our code, we have snippets like the following: json.dumps(**some_model_object.to_dict()) which will serialize a some_model_object to json. Recently, we've decided to expose some internal resources to our users, but some of these resources have specific private instance values that we do not want to transmit back during serialization if the requesting user is not a super user.
I'm trying to come up with a clean design that will allow easier serialization, as well as allow us to serialize to a format other than json. I think this is a pretty good use case for Aspect Oriented Design/Programming, where the aspects respect the requesting access controls and serialize the object based on the requesting user's persmissions.
Here's something similar to what I have now:
from framework import current_request
class User(SQLAlchemyDeclarativeModel):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
private_token = Column(Unicode(4096))
def to_dict(self):
serialized = dict((column_name, getattr(self, column_name))
for column_name in self.__table__.c.keys())
# current request might not be bound yet, could be in a unit test etc.
if current_request and not current_request.user.is_superuser():
# we explicitly define the allowed items because if we accidentally add
# a private variable to the User table, then it might be exposed.
allowed = ['id', 'first_name', 'last_name']
serialized = dict((k, v) for k, v in serialized.iteritems() if k in allowed)
return serialized
As one can see, this is less than ideal because now I have to couple the database model with the current request. While this is very explicit, the request coupling is a code smell and I'm trying to see how to do this cleanly.
One way I've thought about doing it is to register some fields on the model like so:
class User(SQLAlchemyDeclarativeModel):
__tablename__ = 'users'
__public__ = ['id', 'first_name', 'last_name']
__internal__ = User.__exposed__ + ['private_token']
id = Column(Integer, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
private_token = Column(Unicode(4096))
Then, I would have a serializer class that is bound with the current request on every WSGI call that will take the desired serializer. For example:
import simplejson
from framework import JSONSerializer # json serialization strategy
from framework import serializer
# assume response format was requested as json
serializer.register_serializer(JSONSerializer(simplejson.dumps))
serializer.bind(current_request)
Then in my view somewhere, I would just do:
from framework import Response
user = session.query(User).first()
return Response(code=200, serializer.serialize(user))
serialize would be implemented as follows:
def serialize(self, db_model_obj):
attributes = '__public__'
if self.current_request.user.is_superuser():
attributes = '__private__'
payload = dict((c, getattr(db_model_obj, c))
for c in getattr(db_model_obj, attributes))
return self.serialization_strategy.execute(payload)
Thoughts on this approach's readability and clarity? Is this a pythonic approach to the problem?
Thanks in advance.
establish the "serialization" contract via a mixin:
class Serializer(object):
__public__ = None
"Must be implemented by implementors"
__internal__ = None
"Must be implemented by implementors"
def to_serializable_dict(self):
# do stuff with __public__, __internal__
# ...
keep it simple with the WSGI integration. "register", JSONSerializer as an object, and all that is some kind of Java/Spring thing, don't need that fanfare. Below is my pylons 1.0-style solution, I'm not on pyramid yet:
def my_controller(self):
# ...
return to_response(request, response, myobject)
# elsewhere
def to_response(req, resp, obj):
# this would be more robust, look in
# req, resp, catch key errors, whatever.
# xxx_serialize are just functions. don't need state
serializer = {
'application/json':json_serialize,
'application/xml':xml_serialize,
# ...
}[req.headers['content-type']]
return serializer(obj)

Categories

Resources