Flask Python REST API set optional JSON param when receiving POST - python

I'm building a REST API with Flask which add a photo to a database. The database is abstracted in PhotoModel Class. The API receives a JSON formated HTTP POST which contain the picture in a bin string an the name, all the other parameters are optional.
How to construct "photo" object if some param aren't present in the JSON posted?
On the database Model (PhotoModel) I have specify only two compulsory items, so the logic to only take into account params present in the JSON should be in the function bellow.
def add_photo():
"""Add photo to database"""
if request.method == 'POST' and request.headers['Content-Type'] == 'application/json':
photo = PhotoModel(
name = request.json['name'],
device_version = request.json['device_version'],
date = request.json['date'],
picture = request.json['picture'],
comment = request.json['comment']
)
try:
photo.put()
return "200"
except CapabilityDisabledError:
return "500 DB read-only"
else:
return "415 Unsupported Media Type"
I can't figure out how to do it, any pointer would help

Take a look at peewee it comes with a RESTful API in JSON. It's also an light ORM engine.

I've discovered JSON Schema and it works fantastic to validate JSON requests.
Create a decorator which you can use for all views:
from functools import update_wrapper
from jsonschema import validate as jsonschema_validate
def validate(schema):
def decorator(f):
def wrapped_function(*args, **kwargs):
# Validate request Content Type
if request.json is None:
raise ValidationError("Content Type must be JSON")
# Validate document
jsonschema_validate(request.json, schema)
return f(*args, **kwargs)
return update_wrapper(wrapped_function, f)
return decorator
Use decorator for your views:
#app.route('/', methods=['POST'])
#validate(schema)
def insert_document():
# now your request.json object is validated against the specified schema

data = request.get_json() #You can use any method here.
#Below the required parameters outside the try
email=data['email']
role=data['role']
try:
#Here are the optional json parameters inside a try
firstname = data['firstname']
lastname = data['lastname']
except KeyError:
#Here handle the exception, maybe parse some default values.
pass

Related

Flask restful can't map to pathparam Resource

I have a flask restful resource like this:
api.add_resource(TrainerById, '/api/trainer/<int:uuid>')
with the source code like this:
class TrainerById(Resource):
def get(self):
data = trainer_by_id_parser.parse_args()
trainer_uuid = data['uuid']
new_trainer = Trainer.find_by_uuid(trainer_uuid)
if not new_trainer:
return {'msg': f"Trainer with uuid {trainer_uuid} not found"}, 401
else:
return {'msg': to_json_trainer(new_trainer)}
I want to return the trainer profile of the trainer with the UUID from the path param, however the issue is that, it returns a 404 whenever I try to access the endpoint like such:
localhost:5000/api/trainer/profile/886313e1-3b8a-5372-9b90-0c9aee199e5d #gives 404
You mixed resourceful routing with argument parsing.
Resourceful Routing are endpoints of an app.
Listed below are the examples of different routes:
localhost:5000/api/trainer/
localhost:5000/api/trainer/profile
localhost:5000/api/trainer/profile/6385d786-ff51-455e-a23f-0699c2c9c26e
localhost:5000/api/trainer/profile/4385d786-ef51-455e-a23f-0c99c2c9c26d
Note that last two can be grouped by using resourceful routing.
RequestParser is Flask-RESTPlus built-in support for request data validation. Those can be querystring or POST form encoded data etc.
With incomplete code you gave, the functionality you want can be implemented like so:
from flask import Flask
from flask_restplus import Resource, Api
app = Flask(__name__)
api = Api(app)
# List of trainers, just basic example instead of DB.
trainers = [
'6385d786-ff51-455e-a23f-0699c2c9c26e',
'7c6d64ae-8334-485f-b402-1bf08aee2608',
'c2a427d5-5294-4fad-bf10-c61018ba49e1'
]
class TrainerById(Resource):
def get(self, trainer_uuid):
# In here, trainer_uuid becomes <class 'uuid.UUID'>, so you can
# convert it to string.
if str(trainer_uuid) in trainers:
return {'msg': f"Trainer with UUID {trainer_uuid} exists"}, 200
else:
return {'msg': f"Trainer with uuid {trainer_uuid} not found"}, 404
# This means after profile/, next expected keyword is UUID with name in route
# as trainer_uuid.
api.add_resource(TrainerById, '/api/trainer/profile/<uuid:trainer_uuid>')
if __name__ == '__main__':
app.run(debug=True)

Is it possible to define a nested schema with mutually exclusive fields in marshmallow?

I am using marshmallow to validate json data that I am receiving in a flask restful api. In the post request however there is a mutually exclusive field.
For example :
{"predict": {"id": "5hgy667y4h7f"}} or {"predict": {"text": "This is a sample sentence"}}
But NOT both id and text should be sent together. Moreover different methods are called based on weather id or text is received.
Q) How do I construct a schema in marshmallow that allows me to validate the above?
Sample code I have for either one of the fields is below -
from flask import Flask, request
from flask_restful import Resource, Api, abort
from marshmallow import Schema, fields, ValidationError
app = Flask(__name__)
api = Api(app)
class Mutex1(Schema):
text = fields.Str(required=True)
class Meta:
strict = True
class Mutex2(Schema):
id_ = fields.Str(required=True)
class Meta:
strict = True
class MySchema(Schema):
predict = fields.Nested(Mutex1)
class Meta:
strict = True
class Test(Resource):
def post(self):
input_req = request.get_json(force=True)
try:
result = MySchema().load(input_req)
except ValidationError:
return {'message': 'Validation Error'}, 500
else:
return {'message': 'Successful validation'}, 200
api.add_resource(Test, '/test')
app.run(host='0.0.0.0', port=5000, debug=True)
This code accepts only text, and text with id_, however it rejects only id_. Any idea how to make it accept id_ and reject both text and id_ when passed together ?
Create a Mutex schema with both text and id_ and add a schema-level validation to fail if both are provided.
class Mutex(Schema):
#validates_schema
def validate_numbers(self, data):
if (
('text' in data and 'id_' in data) or
('text' not in data and 'id_' not in data)
):
raise ValidationError('Only one of text and _id is allowed')
text = fields.Str()
id_ = fields.Str()
class Meta:
strict = True
Side notes:
Input validation error should not return a 500 (server error) but a 422.
I'm not familiar with flask-restful, but it looks like you could save yourself some boilerplate by using webargs to parse the resource inputs.

Passing a header value to a get request in django

I want to pass a value through the Headers of a get request.
Im trying the below but it doesn't work,
class ListCategoriesView(generics.ListAPIView):
"""
Provides a get method handler.
"""
serializer_class = CategorySerializer
def get(self, request, *args, **kwargs):
token = request.data.get("token", "")
if not token:
"""
do some action here
"""
if not UserAccess.objects.filter(accessToken=token).exists():
"""
do some action here
"""
else:
"""
do some action here
"""
I want to pass the token in the headers like that :
can anyone help me with this issue,
thanks a lot in advance.
You said it yourself, you're passing it in the headers, so you need to get it from there. DRF does not do anything special to access the headers, so it proxies to the underlying Django HttpRequest object, which makes them available via the META attribute, converted to uppercase and prefixed by HTTP_:
token = request.META.get("HTTP_TOKEN", "")

Validating url in POST parameters in Django

I am trying to validate that the parameters in the POST request sent are valid URLs.
This is my views.py
views.py
def post(self, request):
if url_validator(request) == 400:
return Jsonresponse(status=400)
This is my utils.py. This file will contain all general methods and classes.
def url_validator(request, ext):
for key, value in request.data.items():
value = request.data[key]
try:
URLValidator(value)
except ValidationError:
return 400
When I call the function url_validator from views, it executes but doesn't return the exception when either of the request parameters doesn't contain URLs.
For example, if I pass a parameter param1: "some string", it doesn't go through the ValidationError path.
How do I go about getting the correct return from the function?
A validator class doesn't take the input to be validated in its instantiation, it takes it when you call the instantiated object:
validator = URLValidator()
validator(value)
But this really isn't how to do validation in Django. Either use a form, or if you're processing submitted JSON, use a django-rest-framework serializer.

How to implement login required decorator in Flask

I have 2 Flask apps (different projects) that work together . One implements some API which uses tokens for auth. The second one consumes the API and makes a web interface for it. Now I have a login function that sends the username and password to the API, and if correct, gets the auth token in return. Once I have the token, I save it to the session of the user and the user should now be considered as logged in/ autheticated. How can I implement the login_required decorator for such a case.
Here is my login function -
def login(self):
response = make_request(BASE_URL + 'login/', clean_data(self.data))
if response.status_code == 200:
session['auth_token'] = response.json().get('auth_token')
return True
return False
How can I make the login_required decorator?
Also I am using Redis to store sessions if that matters.
Have a look at the official flask docs regarding decorators:
https://flask.palletsprojects.com/en/1.1.x/patterns/viewdecorators/ or the python docs https://www.python.org/dev/peps/pep-0318/ as well.
Your decorator should look something like:
from functools import wraps
from flask import abort
import jwt
def authorize(f):
#wraps(f)
def decorated_function(*args, **kws):
if not 'Authorization' in request.headers:
abort(401)
user = None
data = request.headers['Authorization'].encode('ascii','ignore')
token = str.replace(str(data), 'Bearer ','')
try:
user = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])['sub']
except:
abort(401)
return f(user, *args, **kws)
return decorated_function
... and then in your app.py you may have:
#app.route('/api/game', methods=['POST'])
#authorize
def create(user):
data = json.loads(request.data)
....
In this particular case I have used JWT as token and your token can be different respectively the decoding of the token can be your custom implementation, but the basic mechanisms are pretty much as on the example above.
I would place the following decorator function in somewhere common
def validate_api_token(validation_func):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kws):
api_token = request.headers.get('Authorization')
is_valid_api_token = validation_func(api_token)
if is_valid_api_token:
return f(*args, **kws)
return 'Invalid API Token', 401
return decorated_function
return decorator
For small POC flask apps, if you're ok with storing the tokens in a non-versioned file, the following can work:
# tokens are read from a non-versioned `.tokens` file and loaded into a set
api_tokens = load_api_tokens()
def simple_api_token_validation(api_token):
return api_token in api_tokens
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(simple_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Another simple option is to query against a database (e.g. redis):
redis_session = Redis(host=REDIS_HOST, password=REDIS_PASSWORD)
def redis_api_token_validation(api_token):
if not api_token:
return False
api_token_hash = hashlib.sha256(api_token.encode()).hexdigest()
return redis_session.exists(f'api:tokens:{api_token_hash}')
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(redis_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Best IMO as #Velin answered is to use jwt to validate the token
Given that each subsequent request will contain the API token, the decorator should do the following
Accept a generic request. You can use *args and **kargs for that
Extract the token from the header and compare it with the token stored in db (not Redis, but wherever the token generated is stored in the backend)
If authenticated, the *args and **kargs should be passed on to the decorated function
The output of the decorated function should then be returned as is
If the authentication failed, an error message should be returned.
For explanation on decorators, check out this link:
http://thecodeship.com/patterns/guide-to-python-function-decorators/

Categories

Resources