Simple request parsing without reqparse.RequestParser() - python

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...

Related

Python flask response validation

I am using python rebar for validating request_body_schema, it works well. we can validate input body arguments. Its cool, we do not want to implement any manual input validations like adding if statements.
Same way I could not able to validate response arguments.
But flask_rebar mentioned we can implement Link
Opting In to Response Validation
There are two ways to opt-in to response validation:
Globally, via validate_on_dump attribute of your Rebar instance. Using this method, it is easy to turn on validation for things like test cases, while reaping performance gains by leaving it off in your production endpoints (assuming your API contract testing is sufficient to guarantee that your API can’t return invalid data).
At schema level, via flask_rebar.validation.RequireOnDumpMixin (including if you use our legacy pre-canned ResponseSchema as the base class for your schemas). Any schema that includes that mixin is automatically opted in to response validation, regardless of global setting. Note that in Flask-Rebar 2, that mixin serves only as a “marker” to trigger validation; we plan to augment/replace this with ability to use SchemaOpts as a more logical way of accomplishing the same thing in the near future (https://github.com/plangrid/flask-rebar/issues/252).
But I am not getting any example, Can any body help me with example
my code:
from marshmallow import fields, Schema
from flask import Flask
from flask_rebar import Rebar, RequestSchema, get_validated_body
class CreateAccountSchema(RequestSchema):
email = fields.String(required=True)
country = fields.String(required=True)
default_currency = fields.String(required=True)
class AccountSchema(Schema):
id = fields.String()
email = fields.String()
country = fields.String()
default_currency = fields.String(required=True) # if this is not passed raise error
rebar = Rebar()
registry = rebar.create_handler_registry(prefix="/v1")
#registry.handles(
rule='/accounts',
method='POST',
marshal_schema={201: AccountSchema()},
request_body_schema=CreateAccountSchema(),)
def get_todos():
"""
This docstring will be rendered as the operation's description in
the auto-generated OpenAPI specification.
"""
body = get_validated_body()
body = rebar.validated_body
data = {"id": "myname", "email": "myemail", "country": "any"}
return data, 201
#registry.handles(
rule='/values',
method='GET',
marshal_schema=None,)
def get_values():
"""
This docstring will be rendered as the operation's description in
the auto-generated OpenAPI specification.
"""
data = {"id": "myname", "email": "myemail", "country": "any"}
return 'Hello, Poorvika'
def create_app(name) -> Flask:
app = Flask(name)
rebar.init_app(app)
return app
if __name__ == '__main__':
create_app(__name__).run()
To get the example to work in Flask-Rebar 2.0, you have to replace marshal_schema argument in the #registry.handle decorator with response_body_schema. marshal_schema is the old deprecated name which was removed in Rebar 2.0. The new name response_body_schema was introduced in Rebar 1.7.
response_body_schema is a Marshmallow schema that will be used marshal the return value of the function. marshmallow.Schema.dump will be called on the return value. response_body_schema can also be a dictionary mapping status codes to Marshmallow schemas - see Marshaling. NOTE: In Flask-Rebar 1.0-1.7.0, this was referred to as marshal_schema. It is being renamed and both names will function until version 2.0.
– Basics - Flask-Rebar documentation
https://flask-restplus.readthedocs.io/en/stable/marshalling.html
`#api.marshal_with`
user reponse marshaling

How to use flask app context when using decorators

I'd like to use token authorization from Flask-Security-Too for this "Article" endpoint. However, the decorator function #auth_token_required("token") needs the context of the app. The app is initialized in a different file.
I've added app.app_context() but I don't know how to tell the decorator to use it's context.
Note: I'm working with Flask-RestX for providing a SwaggerUI (and OpenAPI spec). Also, I use Blueprints/Namespaces to modularize the API - which makes it a bit more complex.
from flask import Blueprint
from flask_restx import Api, Resource, fields, Namespace
from flask_security import auth_token_required
from .db import db_session
from .models import Article
from flask import current_app as app
article_blueprint = Blueprint('api', __name__, url_prefix='/api/article')
flask_api = Api(article_blueprint, version='0.1', title='Article Manager',
description='Article management',)
article_manager = Namespace('article', description='Article Management Endpoint')
parser = article_manager.parser()
parser.add_argument('title', type=str, required=False,
help='Title of Article', location='json')
parser.add_argument('text', type=str, required=False,
help='Text of Article', location='json')
parser.add_argument('id', type=int, required=False,
help='ID of Article', location='json')
#article_manager.route('/<string:title>','/<int:id>')
class GetArticle(Resource):
# FIXME app.app_context() ?!
#auth_token_required("token")
def get(self, title=None, id=None):
# removed error handling for simplicity
if title is not None:
data = Article.query.filter(Article.title.ilike('%'+title+'%')).first()
if id is not None:
data = Article.query.filter_by(id=id).first()
db_session.commit()
return {'id': data.id, 'title': data.title, 'text': data.text}, 200
flask_api.add_namespace(article_manager)
Flask tells me:
Exception has occurred: RuntimeError
Working outside of application context.
The problem is in the way of using auth_token_required decorator. auth_token_required decorator unlike auth_required doesn't accept any additional arguments for configuration and expects the only decorated function to be transmitted.
Your code can be fixed by applying of one of the following variants:
#article_manager.route('/<string:title>', '/<int:id>')
class GetArticle(Resource):
#auth_token_required
def get(self, title=None, id=None):
pass
or
#article_manager.route('/<string:title>', '/<int:id>')
class GetArticle(Resource):
#auth_required("token")
def get(self, title=None, id=None):
pass
I am not familiar with flask_restx - however - an app_context is automatically created by flask when it first processes a request. You don't mention when you are getting this error - at config time or actual request time (or is it when you call add_namespace?). You might want to look at: https://flask.palletsprojects.com/en/2.0.x/appcontext/ to learn more about the appcontext.

How to load in arguments using marshmallow and flask_restful?

I have a flask_restful API set up but want users to pass numpy arrays when making a post call. I first tried using reqparse from flask_restful but it doesn't support data types of numpy.array. I am trying to use marshmallow (flask_marshmallow) but am having trouble understanding how the arguments get passed to the API.
Before I was able to do something like:
from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('url', type=str)
parser.add_argument('id', type=str, required=True)
But now I am not sure how to translate that into marshmallow. I have this set up:
from flask_marshmallow import Marshmallow
app = Flask(__name__)
ma = Marshmallow(app)
class MySchema(ma.Schema):
id = fields.String(required=True)
url = fields.String()
But how can I load in what a user passes in when making a call by using my newly defined schema?
I don't know flask-marshmallow and the docs only show how to use it to serialize responses.
I guess to deserialize requests, you need to use webargs, developped and maintained by the marshmallow team.
It has marshmallow integration:
from marshmallow import Schema, fields
from webargs.flaskparser import use_args
class UserSchema(Schema):
id = fields.Int(dump_only=True) # read-only (won't be parsed by webargs)
username = fields.Str(required=True)
password = fields.Str(load_only=True) # write-only
first_name = fields.Str(missing="")
last_name = fields.Str(missing="")
date_registered = fields.DateTime(dump_only=True)
#use_kwargs(UserSchema())
def profile_update(username, password, first_name, last_name):
update_profile(username, password, first_name, last_name)
# ...

Graphene resolver for an object that has no model

I'm trying to write a resolver that returns an object created by a function. It gets the data from memcached, so there is no actual model I can tie it to.
I think my main issue is I can't figure out what type to use and how to set it up. I'm using this in conjunction with Django, but I don't think it's a django issue (afaict). Here's my code so far:
class TextLogErrorGraph(DjangoObjectType):
def bug_suggestions_resolver(root, args, context, info):
from treeherder.model import error_summary
return error_summary.bug_suggestions_line(root)
bug_suggestions = graphene.Field(TypeForAnObjectHere, resolver=bug_suggestions_resolver)
Notice I don't know what type or field to use. Can someone help me? :)
GraphQL is designed to be backend agnostic, and Graphene is build to support various python backends like Django and SQLAlchemy. To integrate your custom backend, simply define your models using Graphene's type system and roll out your own resolvers.
import graphene
import time
class TextLogEntry(graphene.ObjectType):
log_id = graphene.Int()
text = graphene.String()
timestamp = graphene.Float()
level = graphene.String()
def textlog_resolver(root, args, context, info):
log_id = args.get('log_id') # 123
# fetch object...
return TextLogEntry(
log_id=log_id,
text='Hello World',
timestamp=time.time(),
level='debug'
)
class Query(graphene.ObjectType):
textlog_entry = graphene.Field(
TextLogEntry,
log_id=graphene.Argument(graphene.Int, required=True),
resolver=textlog_resolver
)
schema = graphene.Schema(
query=Query
)

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

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)
...

Categories