Flask Hintful¶
Flask-Hintful helps you write Restful APIs using Flask by taking advantage of Python’s type hints.
It deserializes HTTP query/path parameters according to type hints and automatically generates OpenApi documentation for your registered routes.
For serializing/deserializing complex types it supports using Marshmallow and Dataclasses.
Contents¶
Getting Started¶
Creating the FlaskHintful object¶
Import Flask and FlaskHintful, then pass an instance of your Flask application to the constructor of FlaskHintful.
from flask import Flask
from flask_hintful import FlaskHintful
app = Flask('My API')
api = FlaskHintful(app)
Creating your API routes¶
Use type hints to describe your parameters/return types and Flask Hintful will take care of serializing/deserializing them, as well as generating the OpenApi documentation automatically!
@api.route('/')
def get_dataclasses(foo: int, bar: str = None) -> List[DataclassModel]:
'''Returns list of DataclassModel using query args'''
pass
@api.route('/<id>')
def get_dataclass(id: str) -> DataclassModel:
'''Returns DataclassModel using path arg'''
pass
@api.route('/', methods=['POST'])
def create_dataclass(model: DataclassModel) -> DataclassModel:
'''Creates a DataclassModel using POST and request body'''
return model
For Marshmallow Schema see Using Marshmallow Schemas
Registering routes and Blueprints¶
Use the FlaskHintful object to register routes using the @route
decorator
@api.route('/api/test')
def view_func():
pass
Or register a Flask Blueprint that contains your view funcs.
flask_bp = Blueprint('flask_bp', __name__)
@flask_bp.route('/api/test')
def view_func():
pass
api.register_blueprint(flask_bp)
Using Marshmallow Schemas¶
When using an object that has a Marshmallow Schema Flask Hintful needs a reference to that schema.
By default Flask Hintful search for schemas in an attribute __marshmallow__
, if you wish to change that behaviour look at Subclassing.
Your Marshmallow Schema must also return an instance of your model when using load
or loads
, so you’ll have to to use the post_load
decorator to instantiate your object.
Example Marshmallow Schema:
from marshmallow import Schema, fields, post_load
class MarshmallowModel():
def __init__(self,
str_field,
int_field
):
self.str_field = str_field
self.int_field = int_field
class MarshmallowModelSchema(Schema):
str_field = fields.Str()
int_field = fields.Int()
@post_load
def make_some_model(self, data, **kwargs):
return MarshmallowModel(**data)
# Sets MarshmallowModelSchema as a schema for MarshmallowModel
setattr(MarshmallowModel, '__marshmallow__', MarshmallowModelSchema)
# Flask Hintful will pick MarshmallowModel from type hints and use the Schema's dump/load to serialize/deserialize it
@api.route('/', methods=['POST'])
def create_model(model: MarshmallowModel) -> MarshmallowModel:
'''Creates a MarshmallowModel'''
return model
Custom Serializers/Deserializers¶
You can add or replace serializers/deserializers for any “basic” types. For customizing Marshmallow/Dataclass behaviour you’ll need to do Subclassing.
Do note that adding a (de)serializer to an existing type will override the default.
Adding Serializers¶
On your FlaskHintful instance use .serializer.add_serializer
You will need to provide a callable that can receive an instance of the type and return a str
.
Example, custom serializer that serializes bools to TRUE and FALSE:
def my_bool_serializer(data: bool) -> str:
return str(data).upper()
api.serializer.add_serializer(bool, my_bool_serializer)
Adding Deserializers¶
On your FlaskHintful instance use .deserializer.add_deserializer
You will need to provide a callable that can receive a str
and return an instance of that type.
Example, custom deserializer that deserializes 'yay'
to True
:
def my_bool_deserializer(data: str) -> bool:
if data.lower() == 'yay':
return True
return False
api.deserializer.add_deserializer(bool, my_bool_deserializer)
Default Serializers¶
For “basic” types
self.serializers: Dict[Type, Callable] = {
dict: lambda d: json.dumps(d, default=isodate_json_encoder),
str: str,
int: str,
float: str,
bool: str,
date: lambda d: d.isoformat(),
datetime: lambda d: d.isoformat(),
}
Default Deserializers¶
For “basic” types
self.deserializers: Dict[Type, Callable] = {
dict: json.loads,
str: str,
int: int,
float: float,
bool: str_to_bool,
datetime: date_parser,
date: lambda d: date_parser(d).date()
}
Subclassing¶
You can subclass Serializer
or Deserializer
to change how we detect and serialize/deserialize Dataclasses
or Marshmallow Schemas
.
Subclassing Marshmallow¶
Your subclass will need to implement these methods on Serializer:
is_marshmallow_model
serialize_marshmallow_model
serialize_marshmallow_model_to_dict
And these methods on Deserializer:
is_marshmallow_model
deserialize_marshmallow_model
Example:
from typing import T, Type, Union
from flask import Blueprint, Flask
from flask_hintful import Deserializer, FlaskHintful, Serializer
class MySerializer(Serializer):
def is_marshmallow_model(data: T) -> bool:
'''Returns True if Data is a Marshmallow object'''
pass
def serialize_marshmallow_model(data: T) -> str:
'''Serializes Marshmallow object data into a JSON str'''
pass
def serialize_marshmallow_model_to_dict(data: T) -> dict:
'''Serializes Marshmallow object data into a dict'''
pass
class MyDeserializer(Deserializer):
@staticmethod
def is_marshmallow_model(type_: Type) -> bool:
'''Returns True if type_ is a marshmallow'''
pass
@staticmethod
def deserialize_marshmallow_model(data: Union[str, dict], type_: Type) -> T:
'''Returns deserialized data into instance of type_'''
pass
app = Flask('My API')
api = FlaskHintful(app,
serializer=MySerializer(),
deserializer=MyDeserializer())
Subclassing Dataclass¶
Your subclass will need to implement these methods on Serializer:
is_dataclass
serialize_dataclass
serialize_dataclass_to_dict
And these methods on Deserializer:
is_dataclass
deserialize_dataclass
Example:
from typing import T, Type, Union
from flask import Blueprint, Flask
from flask_hintful import Deserializer, FlaskHintful, Serializer
class MySerializer(Serializer):
def is_dataclass(data: T) -> bool:
'''Returns True if Data is a Dataclass'''
pass
def serialize_dataclass(data: T) -> str:
'''Serializes Dataclass data into a JSON str'''
pass
def serialize_dataclass_to_dict(data: T) -> dict:
'''Serializes Dataclass data into a dict'''
pass
class MyDeserializer(Deserializer):
@staticmethod
def is_dataclass(type_: Type) -> bool:
'''Returns True if type_ is a Dataclass'''
pass
@staticmethod
def deserialize_dataclass(data: Union[str, dict], type_: Type) -> T:
'''Returns deserialized data into instance of type_'''
pass
app = Flask('My API')
api = FlaskHintful(app,
serializer=MySerializer(),
deserializer=MyDeserializer())
OpenApi Documentation¶
OpenApi documentation is automatically generated by Flask Hintful.
To access the SwaggerUI the default route is /swagger
. The default route for the OpenApi JSON specfication is /openapi.json
Security¶
You can tell OpenApi which kind of security your application is expecting.
Currently, only these authentication types are supported: Bearer, ApiKey, Basic
app = Flask(__name__)
api = FlaskHintful(app, openapi_security=['bearer', 'apikey', 'basic'])
Note: This is just for OpenApi documentation, Flask-Hintful does not handle authentication/authorization.
Custom Routes¶
You can change the routes that we are serving the SwaggerUI and OpenApi JSON using these two configurations in your Flask App.
app = Flask(__name__)
app.config['FLASK_HINTFUL_OPENAPI_JSON_URL'] = '/custom_openapi.json'
app.config['FLASK_HINTFUL_OPENAPI_UI_URL'] = '/custom_swagger'
api = FlaskHintful(app)