I'm using Flask-Restful to create an API to store geojson data. Geojson allows for storing 'properties', and makes no restrictions on what these parameters can be (could store a color, a nickname, etc.) I would like to transmit and store this data using flask-restful, but I'm not sure that I can do this with 'open-ended' data. It appears when I use 'marshal' for my data, I need to specify exactly the fields I expect.
from flask import Flask, abort
from flask.ext.restful import Api, Resource, fields, marshal, reqparse
class GeoAPI(Resource):
def get(self, id):
geo= session.query(data.Geo).filter_by(id= id).first()
if (geo):
return {'geo': marshal(geo, geo_fields)}
else:
abort(404)
geo_fields = {
"name": fields.String,
"coordinates": fields.List(fields.List(fields.List(fields.Float))),
"parameters": fields.String, # String may be the wrong type, tried nested?
'version': fields.String,
'uri': fields.Url('geo')
}
api.add_resource(GeoAPI, '/pathopt/api/0.1/geos/<int:id>', endpoint = 'geo')
The data for Geo pulls from a SQLAlchemy query.
Is it possible to state that 'properties' is an object which can contain many different fields, or does this require me to explicitly state my field names?
Related
Does the flask_restplus library allows to define a model once and re-use it in multiple namespaces. In my use case, I would like to have a general model for returning on successful post requests. At the moment, I have to redefine the model in every namespace.
API with two namespaces
from flask_restplus import Api
from api_service.api.api_1 import api as ns_1
from api_service.api.api_2 import api as ns_2
api = Api()
api.add_namespace(ns_1, path='/api_1')
api.add_namespace(ns_2, path='/api_2')
Code snippet, I have to redefine in every namespace
post_response_model = api.model("Post response", {
'message': fields.String(),
'id': fields.String(description="Id of inserted entry")
})
Overview
I am using Flask-SqlAlchemy and now I am looking into marshmallow to help me serialize and deserialize request data.
I was able to successfully:
Create my models using Flask-SqlAlchemy
Use Flask-Marshmallow to serialize database objects using the same model, by using the Optional Flask-SqlAlchemy Integration
Use marshmallow-jsonapi to quickly generate Json API compliant responses. This required me to declare new Schemas to specify which attributes I want to include (this is duplicate from Flask-SqlAlchemy Models)
Code Samples
Flask-SqlAlchemy Declarative Model
class Space(db.Model):
__tablename__ = 'spaces'
id = sql.Column(sql.Integer, primary_key=True)
name = sql.Column(sql.String)
version = sql.Column(sql.String)
active = sql.Column(sql.Boolean)
flask_marshmallow Schema Declaration (Inherits from SqlAlchemy Model)
ma = flask_marshmallow.Marshmallow(app)
class SpaceSchema(ma.ModelSchema):
class Meta:
model = Space
# API Response
space = Space.query.first()
return SpaceSchema().dump(space).data
# Returns:
{
'id': 123,
'version': '0.1.0',
'name': 'SpaceName',
'active': True
}
marshmallow_json api - requires new Schema Declaration, must include each attribute and type manually
class SpaceJsonSchema(marshmallow_json.Schema):
id = fields.Str(dump_only=True)
name = fields.Str()
version = fields.Str()
active = fields.Bool()
class Meta:
type_ = 'spaces'
self_url = '/spaces/{id}'
self_url_kwargs = {'id': '<id>'}
self_url_many = '/spaces/'
strict = True
# Returns Json API Compliant
{
'data': {
'id': '1',
'type': 'spaces',
'attributes': {
'name': 'Phonebooth',
'active': True,
'version': '0.1.0'
},
'links': {'self': '/spaces/1'}
},
'links': {'self': '/spaces/1'}
}
Issue
As shown in the code, marshmallow-jsonapi allows me to create json api compliant responses, but I end up having to maintain a Declarative Model + Schema Response model.
flask-marshmallow allows me to create Schema responses from the SqlAlchemy models, so I don't have to maintain a separate set of properties for each model.
Question
Is it at all possible to use flask-marshmallow and marshmallow-jsonapi together so 1. Create Marshmallow Schema from a SqlAlchemy model, AND automatically generate json api responses?
I tried creating Schema declaration that inherited from ma.ModelSchema and marshmallow_json.Schema, in both orders, but it does not work (raises exception for missing methods and properties)
marshmallow-jsonapi
marshmallow-jsonapi provides a simple way to produce JSON
API-compliant data in any Python web framework.
flask-marshmallow
Flask-Marshmallow includes useful extras for integrating with
Flask-SQLAlchemy and marshmallow-sqlalchemy.
Not a solution to this exact problem but I ran into similar issues when implementing this library : https://github.com/thomaxxl/safrs (sqlalchemy + flask-restful + jsonapi compliant spec).
I don't remember exactly how I got around it, but if you try it and serialization doesn't work I can help you resolve it if you open an issue in github
I'm trying to simplify endpoint data validation for a flask application, with a Swagger endpoint spec. I've avoided using the entire swagger-generated server code, because I find connexion too opaque. Instead I use the model objects generated by Swagger, but write the controllers myself. For example, this is the endpoint for searching for restaurants nearby. The user needs to provide either a zip_code or a combination latitude and longitude, and those values need to be validated:
from flask import jsonify, request, Response
from models import RestaurantSearchRequest, RestaurantSearchResponse
app_blueprint = Blueprint(__name__, __name__, url_prefix='/restaurants')
#app_blueprint.route(
utils.versioned_api_route('/search/'), methods=['GET'])
def find_restaurants_by_zip_or_lat_lng() -> Response:
request_data = utils.multidict_to_dict(request.args)
try:
search_request = RestaurantSearchRequest.from_dict(request_data)
search_response = RestaurantSearchResponse.from_lat_lng_zip(**search_request.to_dict())
return Response(jsonify(search_response.to_dict()), 200)
except ValueError as ex:
return Response(ex.message, 400)
Right now the data validation is being done in the model using Voluptuous, but it requires a lot of boilerplate:
from voluptuous.schema_builder import Schema
from lib import validators
from models.swagger import RestaurantSearchRequest as SwaggerRestaurantSearchRequest
# Inherit all swagger utils, such as `to_dict` and `from_dict`
class RestaurantSearchRequest(SwaggerRestaurantSearchRequest):
def __init__(self: 'RestaurantSearchRequest', latitude: float=None, longitude: float=None, zip_code: str=None):
schema = Schema({
'zip_code': validators.validate_zip_code,
'latitude': validators.validate_latitude,
'longitude': validators.validate_longitude,
})
schema({
'zip_code': zip_code,
'latitude': latitude,
'longitude': longitude,
})
super(RestaurantSearchRequest, self).__init__(latitude, longitude, zip_code)
if (self.longitude is None or self.latitude is None) and self.zip_code is None:
raise ValueError('Must provide either zip code or lat/lng')
I'd also like to get the logic out of the model and move it to a decorator in the view function. The key to all of this is having to the flexability to validate individual fields, as well validate the combination of fields, like above.
Are there any existing libraries out there I can leverage, or is there generic validator that I can use for something like this?
In my Flask-RESTful API, imagine I have two objects, users and cities. It is a 1-to-many relationship. Now when I create my API and add resources to it, all I can seem to do is map very easy and general URLs to them. Here is the code (with useless stuff not included):
class UserAPI(Resource): # The API class that handles a single user
def __init__(self):
# Initialize
def get(self, id):
# GET requests
def put(self, id):
# PUT requests
def delete(self, id):
# DELETE requests
class UserListAPI(Resource): # The API class that handles the whole group of Users
def __init__(self):
def get(self):
def post(self):
api.add_resource(UserAPI, '/api/user/<int:id>', endpoint='user')
api.add_resource(UserListAPI, '/api/users/', endpoint='users')
class CityAPI(Resource):
def __init__(self):
def get(self, id):
def put(self, id):
def delete(self, id):
class CityListAPI(Resource):
def __init__(self):
def get(self):
def post(self):
api.add_resource(CityListAPI, '/api/cities/', endpoint='cities')
api.add_resource(CityAPI, '/api/city/<int:id>', endpoint='city')
As you can see, I can do everything I want to implement a very basic functionality. I can GET, POST, PUT, and DELETE both objects. However, my goal is two-fold:
(1) To be able to request with other parameters like city name instead of just
city id. It would look something like:
api.add_resource(CityAPI, '/api/city/<string:name>', endpoint='city')
except it wouldn't throw me this error:
AssertionError: View function mapping is overwriting an existing
endpoint function
(2) To be able to combine the two Resources in a Request. Say I wanted to get all the
users associated with some city. In REST URLs, it should look something like:
/api/cities/<int:id>/users
How do I do that with Flask? What endpoint do I map it to?
Basically, I'm looking for ways to take my API from basic to useful.
Your are making two mistakes.
First, Flask-RESTful leads you to think that a resource is implemented with a single URL. In reality, you can have many different URLs that return resources of the same type. In Flask-RESTful you will need to create a different Resource subclass for each URL, but conceptually those URLs belong to the same resource. Note that you have, in fact, created two instances per resource already to handle the list and the individual requests.
The second mistake that you are making is that you expect the client to know all the URLs in your API. This is not a good way to build APIs, ideally the client only knows a few top-level URLs and then discovers the rest from data in the responses from the top-level ones.
In your API you may want to expose the /api/users and /api/cities as top-level APIs. The URLs to individual cities and users will be included in the responses. For example, if I invoke http://example.com/api/users to get the list of users I may get this response:
{
"users": [
{
"url": "http://example.com/api/user/1",
"name": "John Smith",
"city": "http://example.com/api/city/35"
},
{
"url": "http://example.com/api/user/2",
"name": "Susan Jones",
"city": "http://example.com/api/city/2"
}
]
}
Note that the JSON representation of a user includes the URL for that user, and also the URL for the city. The client does not need to know how to build these, because they are given to it.
Getting cities by their name
The URL for a city is /api/city/<id>, and the URL to get the complete list of cities is /api/cities, as you have it defined.
If you also need to search for cities by their name you can extend the "cities" endpoint to do that. For example, you could have URLs in the form /api/cities/<name> return the list of cities that match the search term given as <name>.
With Flask-RESTful you will need to define a new Resource subclass for that, for example:
class CitiesByNameAPI(Resource):
def __init__(self):
# ...
def get(self, name):
# ...
api.add_resource(CitiesByNameAPI, '/api/cities/<name>', endpoint = 'cities_by_name')
Getting all the users that belong to a city
When the client asks for a city it should get a response that includes a URL to get the users in that city. For example, let's say that from the /api/users response above I want to find out about the city of the first user. So now I send a request to http://example/api/city/35, and I get back the following JSON response:
{
"url": "http://example.com/api/city/35",
"name": "San Francisco",
"users": "http://example/com/api/city/35/users"
}
Now I have the city, and that gave me a URL that I can use to get all the users in that city.
Note that it does not matter that your URLs are ugly or hard to construct, because the client never needs to build most of these from scratch, it just gets them from the server. This also enables you to change the format of the URLs in the future.
To implement the URL that gets users by city you add yet another Resource subclass:
class UsersByCityAPI(Resource):
def __init__(self):
# ...
def get(self, id):
# ...
api.add_resource(UsersByCityAPI, '/api/cities/<int:id>/users', endpoint = 'users_by_city')
I hope this helps!
you can do the id/name thing without duplicating the resource:
api.add_resource(CitiesByNameAPI, '/api/cities/<name_or_id>', endpoint = 'cities_by_name')
class CitiesByNameAPI(Resource):
def get(self, name_or_id):
if name_or_id.isdigit():
city = CityModel.find_by_id(name_or_id)
else:
city = CityModel.find_by_name(name_or_id)
if city:
return city.to_json(), 200
return {'error': 'not found'}, 404
not sure if there are any negative effects from this.
I am running into a problem trying to include tastypie resources in a larger json response in a regular django view. I would like to have the view return something like this (based on a queryset generated in the view, not from typical tastypie get params):
{
"success": bool,
"message": string,
"error": string,
"objects": [
{
"field_one": bar,
"field_two": foo
}
... more objects ...
]
}
where objects list is a list of serialized tastypie resources, and success, message and error are coming from somewhere else in the view.
Right now, I can't figure out how to avoid turing the serialized resource into strings before the larger dict gets build, so I have something like this currently:
{
"success": bool,
"message": string,
"error": string,
"objects": [
"{"field_one": bar, "field_two": foo..."}",
"{"field_one": baz, "field_two": foobar..."}",
...
]
}
The whole point of this is to keep the model json representations consistent, to minimize friction between using the tastypie api directly, and using the data returned in these views. I'm thinking the solution is to somehow use the full_dehydrate method on each resource without serializing them, and then adding them to the bigger dict, and then serializing that dict, but I'm not sure what serializer to use. Or, maybe there is a better way.
As is often the case, writing this up helped me find a temporary solution. Maybe someone will have some input on how to make this better.
I am using this to prepare a queryset for serialization:
def serialize_queryset(resource_class, queryset):
# hand me a queryset, i give you dehydrated resources
resource = resource_class()
dd = {}
# make meta
dd['meta'] = {}
dd['meta']['limit'] = 1000
dd['meta']['next'] = None
dd['meta']['offset'] = 0
dd['meta']['previous'] = None
dd['meta']['total_count'] = len(queryset)
# objects
dd['objects'] = []
for obj in queryset:
bundle = resource.build_bundle(obj=obj)
dehydrated_obj = resource.full_dehydrate(bundle)
dd['objects'].append(dehydrated_obj)
# return dict
return dd
And I use the Serializer from tastypie.serializer. and in using it in a sample view is goes something like:
from tastypie.serializer import Serializer
serializer = Serializer()
def my_view(request):
#... do some other view stuff ...
# prepare a queryset for serialization
queryset = MyModel.objects.filter(is_cool=True)
data = serialize_queryset(MyModel, queryset)
# add in custom stuff, determined earlier in the view somewhere
data['success'] = success
data['message'] = message
data['error'] = error
# serialize and return response
data = serializer.serialize(data)
return HttpResponse(data, mimetype='application/json')
This seems to work. Maybe you see something bad about this method, or a way to improve it?