GithubHelp home page GithubHelp logo

milibris / flask-rest-jsonapi Goto Github PK

View Code? Open in Web Editor NEW
598.0 28.0 153.0 672 KB

Flask extension to build REST APIs around JSONAPI 1.0 specification.

Home Page: http://flask-rest-jsonapi.readthedocs.io

License: MIT License

Python 100.00%
flask jsonapi marshmallow sqlalchemy

flask-rest-jsonapi's People

Contributors

akira-dev avatar arjaanbuijk avatar artezio-andreym avatar carpe-diem avatar chenl avatar congenica-andrew avatar donnyvdm avatar evgg avatar fdegiuli avatar greengiant avatar hellupline avatar holgerpeters avatar idkdataverse avatar jcampbell avatar jmileson avatar jortdebokx avatar karec avatar kumy avatar miks avatar multimeric avatar natureshadow avatar photous avatar raveline avatar rgant avatar rubenghio avatar satwikkansal avatar sodre avatar vilyus avatar zyzzx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-rest-jsonapi's Issues

issue using 'include' to request multiple related resources

Hi there. I'm having trouble requesting multiple related resources and i can't tell if i'm misunderstanding ember-data or the API spec or if there's a problem with the backend library i'm using.

Example: Articles hasMany Comments, Comments belongsTo Author, Comments belongsTo Approver.

I want to load a specific Article along with all Comments and for each Comment, the Author and Approver.

If i do /articles/1?include=comments,comments.author,comments.approver, i get the Article with the Comments and the Approvers, but no Authors.

If i do /articles/1?include=comments,comments.approver,comments.author, i get the Article with the Comments and the Authors, but no Approvers.

I am very confident that i have the Schemas set up correctly, if i remove either of the 'comments.*' options from the query string, the data for the other one is loaded fine. The only issue occurs when i try to ask for both at the same time. I believe that JSONAPI should support this so i believe this is a bug?

Thanks for any help you can provide!

Support offset/limit pagination strategy

Currently used page[number]/page[size] pagination strategy is less flexible than offset/limit strategy. Getting page by number and size is easy and efficient with offset/limit, but getting data given offset and limit can be quite inefficient if we can only set page.

For example, the worst case. We want to get x rows with offset x - 1. We need to get 2x - 1 rows and cut requited locally. If we were using offset/limit pagination strategy we would have downloaded almost twice less data from our server.

It would be better if flask-rest-jsonapi supported both strategies.

api example SQLALCHEMY warning

FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning. 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False resolved this warning.

collection resources without related resources are omitted from collection

In 0.13.0+ when you query a collection with included relations, any collection items that do not have a relation item are omitted from the response data.

I took the example project and reproduced the bug here.

For example, in a GET request to /persons?include=events, any person resource that does not have an associated event resource will be returned in 0.12.6 but not 0.13.0.

With 0.12.6:

{
   data:[
      {
         type:"person",
         relationships:{
            events:{
               links:{
                  self:"/persons/1/relationships/events",
                  related:"/events?id=1"
               },
               data:[ ]
            }
         },
         attributes:{
            name:"Bob"
         },
         id:"1",
         links:{
            self:"/persons/1"
         }
      }
   ],
   links:{
      self:"/persons?include=events"
   },
   meta:{
      count:1
   },
   jsonapi:{
      version:"1.0"
   }
}

With 0.13.0:

{
   data:[ ],
   links:{
      self:"/persons?include=events"
   },
   meta:{
      count:1
   },
   jsonapi:{
      version:"1.0"
   }
}

Support for simple filters

Flask-Resltess in there new beta version has included simple filters. Like:

# instead of
filter=[{"name": "field", "op": "eq", "val": "field_value"}]
# one can use for equal operations
filter[field]=field_value

This approach makes urls look less ugly (as it removes most of the special characters thus encoding uri gives a cleaner result) and shorter urls to worry about.

Authentification & limitation of operation

Hi,
thanks for building this module. Have you considered how user authentification and limits to operation (e.g. only allow Read) could be handled? Something like the decorators from Flask-Security would be neat!
Cheers,
Sebastian

Integrating with flask-jwt-extended

I'm trying to use flask-jwt-extended for authentication management. I can use jwt_required decorator for Resources and it works fine. However I'm having problems with using identity inside before_update_object method. I can load the current identity with get_jwt_identity() and it is dictionary. However if I try to access a key within the dictionary it throws an error. It works fine in before_create_object method though.

class StuffDetail(ResourceDetail):
    def before_update_object(self, obj, data, view_kwargs):
        current_user = get_jwt_identity()
        if int(obj.user.id != current_user.id):
            raise JsonApiException(detail="You are not authorized to edit this resource.",
                                   status=403,
                                   title="Authorization Error")

    schema = StuffSchema
    data_layer = {'session': db.session,
                  'model': Stuff,
                  'methods': {''before_update_object' : before_update_object}
                  }
    decorators = (jwt_required,)
Traceback (most recent call last):
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1994, in __call__
    return self.wsgi_app(environ, start_response)
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "E:\WEB\Project1\flask\lib\site-packages\flask\app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "E:\WEB\Project1\flask\lib\site-packages\flask_jwt_extended\view_decorators.py", line 35, in wrapper
    return fn(*args, **kwargs)
  File "E:\WEB\Project1\flask\lib\site-packages\flask_rest_jsonapi\decorators.py", line 32, in wrapper
    return func(*args, **kwargs)
  File "E:\WEB\Project1\flask\lib\site-packages\flask\views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "E:\WEB\Project1\flask\lib\site-packages\flask_rest_jsonapi\resource.py", line 72, in dispatch_request
    raise e
TypeError: __init__() takes at least 3 arguments (4 given)

Decorator wrapping when creating new Resource class

Every time new Resource class instantiates, methods (get, post, patch, put) are wrapped with check_headers decorator. Wrapping occures on class level. For example, after creating ten Resource classes, there are ten same decorators wrapping get method.
https://github.com/miLibris/flask-rest-jsonapi/blob/master/flask_rest_jsonapi/resource.py#L39
Since Flask creates new instance of View class (which is inherited by Resource) with every incoming request, in the end there are lots of same decorators wrapping (get, post, patch, put) methods. That causes RecursionError at some point.

Marshmallow Version Requirement

The marshmallow verison is currently fixed to 2.13.1 which makes it hard if someone (like me) wants to integrate flask-rest-jsonapi into a existing application which uses another verion of marshmallow.

marshmallow_jsonapi requires 'marshmallow>=2.3.0'. does flask-rest-jsonapi depend on any specific features of marshmallow which where introduced after version 2.3.0? Basically between 2.3.0 and 2.13.1, no backward incompatible changes should have been made.

I sugest to change the requirement to marshmallow>=2.3.0 and maybe make it more future proof with marshmallow>=2.3.0, <3 because version 3 of marhsmallow might introduce breaking changes.

Support nested attributes

The JSONAPI spec specifically leaves latitude for the types of an object's attributes, including attributes of an object that are themselves an array or have some other join conditions but are not related objects (to ensure they cannot be individually addressed and/or are required).

That allows us to express one-to-many semantics even for required attributes, and to guarantee those attributes are present when an object is created or updated.

For example, we might want to require "tags" for people objects:

{
  "data": {
    "attributes": {
       "name": "James",
       "person_tags": [
                {
                    "key": "k1",
                    "value": "v1"
                },
                {
                    "key": "k2",
                    "value": "v2"
                }
            ]
    },
    "type": "person"
}

SQLAlchemy and Marshmallow support this feature via the relationship option and List and Nested field types, but the current create_object cannot handle this use case.

class Person_Tags(db.Model):
    id = db.Column(db.Integer, db.ForeignKey('person.id'), primary_key=True, index=True)
    key = db.Column(db.String, primary_key=True)
    value = db.Column(db.String, primary_key=True)


class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    person_tags= db.relationship("Person_Tags")

## Note we inherit from Marshmallow schema instead of Marshmallow JSONAPI schema here
class PersonTagsSchema(mSchema):
    class Meta:
        type_ = 'person_tags'

    id = fields.Str(dump_only=True)
    key = fields.Str()
    value = fields.Str()


class PersonSchema(Schema):
    class Meta:
        type_ = 'person'
        self_view = 'person_detail'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'person_list'

    id = fields.Str(dump_only=True)
    name = fields.Str()
    person_tags = fields.List(fields.Nested(PersonTagsSchema))

Filter related objects after getting relationship

I am working on a problem that needs filtering out related objects that doesn't belong to the currently logged in user, which means I can't implement the query filter in my model or database.

Here, I intend to override after_get_relationship method and apply my filter there, but according to how it's called in get_relationship, the filter won't be applied thus doesn't affect the final result. I also tried iterating through the list of related_objects and call del on objects that needs to be filtered, but that didn't help.

My question is what should I do to achieve what I intended?

Incorrect last_page value in pagination

The current calculation of last_page value is:

last_page = int(ceil(object_count / page_size))

Which means when my object_count is 43 and page_size is 10, last_page will be 4 while it should be 5 instead.

My current workaround is to set app.config['PAGE_SIZE'] = 10.0 instead of just 10 :) However, this won't work when we specify page size as an url parameter like this: ?page[size]=20

Refer: #64

before_post() is throwing TypeError

def before_post(data=None, *args, **kwargs):
          if not data:
              raise ObjectNotFound({'parameter': 'data'},
                                   "Not a POST request or No data supplied")
 
          code = data['data']['attributes']['code']
          publisher = self.session.query(Device).filter_by(code=code).first()
          if not publisher:
              raise ObjectNotFound({'parameter': 'code'},
                                   "No publisher device found with provided code")
 
          publisher_id = publisher.id
          data['data']['attributes']['publisher_id'] = publisher_id
          del data['data']['attributes']['code']

          pass

This throws-

Traceback (most recent call last): File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1997, in __call__ return self.wsgi_app(environ, start_response) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app response = self.handle_exception(e) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception reraise(exc_type, exc_value, tb) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app response = self.full_dispatch_request() File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request rv = self.handle_user_exception(e) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception reraise(exc_type, exc_value, tb) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request rv = self.dispatch_request() File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/app.py", line 1598, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask_rest_jsonapi/decorators.py", line 33, in wrapper return func(*args, **kwargs) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask/views.py", line 84, in view return self.dispatch_request(*args, **kwargs) File "/var/www/html/vrxis_rest_json/lib/python2.7/site-packages/flask_rest_jsonapi/resource.py", line 77, in dispatch_request raise e TypeError: before_post() got multiple values for keyword argument 'data'

I have tried before_post(args, kwargs, data=None) too. Same result. Please, let me know if I have missed something. Using 0.13.2 as of now.

Unable to add blueprints without errors

I don't know if this is an issue or just my ignorance but I am having difficulty adding my api routes with a blue print prefix:

app/api/views:

from app.api.resources import UserList, UserDetail, MaterialList, MaterialDetail
from flask import Blueprint
from flask_rest_jsonapi import Api

json_api = Blueprint('api', __name__, url_prefix='/api')  # @todo figure out how to add blueprints
api = Api(blueprint=json_api)

# User routes
api.route(UserList, 'user_list', '/users')
api.route(UserDetail, 'user_detail', '/users/<string:id>')

# Material routes
api.route(MaterialList, 'material_list', '/materials')
api.route(MaterialDetail, 'material_detail', '/materials/<string:id>')

app/init.py

# -*- coding: utf-8 -*-
from flask import Flask
from app.api.views import api
from app.database import db
from app.cors import cors
from app.auth.views import auth


def create_app():
    app = Flask(__name__)
    app.config.from_object('config')
    db.init_app(app)
    cors.init_app(app)
    api.init_app(app)
    app.register_blueprint(auth)
    return app


def setup_database(app):
    with app.app_context():
        db.create_all()


app = create_app()
setup_database(app)

Now if I visit http://localhost:5000/api/materials I receive the following error:

werkzeug.routing.BuildError: Could not build url for endpoint 'material_list'. Did you mean 'api.material_list' instead?

The obvious solution that springs to mind would be to change the endpoint name in routes and schemas to: api.material_list

however the error is now:

werkzeug.routing.BuildError: Could not build url for endpoint 'api.material_list'. Did you mean 'api.api.material_list' instead?

If I remove the blueprint:

api = Api()

and visit: http://localhost:5000/materials everything works perfectly!

I've tried defining the blueprint in every way conceivable but I'm still getting the same error. If this is not an issue and I'm just being a noob please forgive me.

AttributeError: 'SqlalchemyDataLayer' object has no attribute 'resource'

I'm trying to move from flask-restless to rest-jsonapi, but i'm getting an attributeerror as soon as i include a resource.

e.g. in my model i have a user-class:

class User(Base):
    uid = db.Column(db.String(100), unique = True)
    password = db.Column(PasswordType(schemes=app.config['PASSWORD_SCHEMES']), nullable=False)
    name = db.Column(db.String(100))
    email = db.Column(EmailType)

with this schema:

class UserSchema(Schema):
    class Meta:
        type_ = 'user'
        self_view = 'user_detail'
        self_view_kwargs = {'id':'<id>'}
        self_view_many = 'user_list'

    uid = fields.Str(dump_only=True)
    name = fields.Str(required=True,load_only=True)
    email = fields.Email(load_only=True)
    display_name = fields.Function(lambda obj: "{} <{}>".format(obj.name, obj.email))
    projects=Relationship(self_view='user_projects',
                          self_view_kwargs={'id':'<id>'},
                          related_view='project_list',
                          related_view_kwargs={'id':'<id>'},
                          many=True,
                          schema='ProjectSchema',
                          type_='project')

but including

class UserList(ResourceList):
    schema = UserSchema
    data_layer = {'session': db.session,
                  'model ': User}

causes:

File "/home/tzimme/.local/lib/python3.5/site-packages/project/rest.py", line 265, in
class UserList(ResourceList):
File "/home/tzimme/.local/lib/python3.5/site-packages/flask_rest_jsonapi/resource.py", line 39, in new
rv._data_layer = data_layer_cls(data_layer_kwargs)
File "/home/tzimme/.local/lib/python3.5/site-packages/flask_rest_jsonapi/data_layers/alchemy.py", line 26, in init
.format(self.resource.name))
AttributeError: 'SqlalchemyDataLayer' object has no attribute 'resource'

Reducing boilerplate?

Have you considered reducing the amount of configuration boilerplate required? Are you open to pull requests which help implement defaults? Prompted by reading the Logical data abstraction docs- the comment says we can see that Person has an attribute named โ€œpasswordโ€ but it's not actually easy to see that due to the duplication of most of the fields and related configuration.

Suggestions:

  • automatically generate a marshmallow schema from the data layer and describe only differences in the schema layer when/where necessary.
  • automatically generate default endpoints

I understand that convention over configuration is a bit more of a Ruby/Rails thing, but every extra bit of code introduces the possibility of error.

In comparison, I've used Python eve before which has seems to have defaults (see https://web.archive.org/web/20170809171457/http://python-eve.org/config.html#global-configuration) and Flask-Restless seems to have sane defaults as well.

How to filter by null relationship?

How can I filter a relationship using a null value? Flask-Restless usesis_null or is_not_null, but there doesn't seem to be a similar operator in Flask-Rest-JSONAPI. Any recommendations on how to achieve the same result in Flask-Rest-JSONAPI?

using recursive query in jsonapi

hi, please how do i place this statement in resourcelist?

cat_ids = [cid for cid, in db.session.query(Catalog.category_id).filter(Catalog.category_id.isnot(None)).distinct(Catalog.category_id)]
listed = db.session.query(Category).join(Catalog).options(contains_eager(Category.products)).filter(Category.id.in_(cat_ids)).all()
newlist = [l.path_to_root()[-1] for l in listed]
new_list = list(set(newlist))

this query list output list of categories, but i m having hard time inserting this in flask-rest-jsonapi,

can anyone please guide?

here is my full code


import os
import random
from sqlalchemy.orm import relationship, backref, contains_eager
from core import db, api
from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
from flask_rest_jsonapi.exceptions import ObjectNotFound
from sqlalchemy.orm.exc import NoResultFound
from marshmallow_jsonapi.flask import Schema, Relationship
from marshmallow_jsonapi import fields

from core.main import main
from flask import render_template, redirect, request, url_for, flash, make_response, abort, current_app, jsonify


from datetime import datetime

from core.main.model import Category, Catalog



class Category(db.Model, BaseNestedSets):
    id = Column(Integer, primary_key=True)
    name = Column(String(400), index=True, unique=False)
    image = Column(String(400), index=True, unique=False)
    # image = Column(ImageColumn(thumbnail_size=(30, 30, True), size=(300, 300, True)))
    products = relationship("Catalog", backref='categs')
    # TODO do a multi-joins and filter in order to fetch sizes and colors from categories.

    @classmethod
    def get_parents_list(cls, db, category_id):
        beginning_getter = db.session.query(cls).filter(cls.id == category_id).cte(name='parent_for', recursive=True)
        with_recursive = beginning_getter.union_all(db.session.query(cls).filter(cls.id == beginning_getter.c.parent_id))
        return db.session.query(with_recursive)

    @classmethod
    def get_children_list(cls, db, catalog, category_id):
        beginning_getter = db.session.query(cls).filter(cls.id == category_id).cte(name='children_for', recursive=True)
        with_recursive = beginning_getter.union_all(db.session.query(cls).filter(cls.parent_id == beginning_getter.c.id))
        return db.session.query(with_recursive).join(catalog).distinct(catalog.category_id)

    def __repr__(self):
        return self.name

    def __str__(self):
        return self.name



# Create logical data abstraction (same as data storage for this first example)
class CategorySchema(Schema):
    class Meta:
        type_ = 'category'
        self_view = 'category_list'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'category_list'

    id = fields.Integer(as_string=True, dump_only=True)
    name = fields.Str(requried=True, load_only=True)
    products = Relationship(self_view='category_list',
                             self_view_kwargs={'id': '<id>'},
                             related_view='category_list',
                             related_view_kwargs={'id': '<id>'},
                             many=True,
                             schema='CategorySchema',
                             type_='categories')






class CatalogSchema(Schema):
    class Meta:
        type_ = 'catalog'
        self_view = 'catalog_list'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'catalog_list'

    id = fields.Integer(as_string=True, dump_only=True)
    name = fields.Str(requried=True, load_only=True)
    products = Relationship(self_view='category_list',
                             self_view_kwargs={'id': '<id>'},
                             related_view='category_list',
                             related_view_kwargs={'id': '<id>'},
                             many=True,
                             schema='ComputerSchema',
                             type_='computer')


cat_ids = [cid for cid, in db.session.query(Catalog.category_id).filter(Catalog.category_id.isnot(None)).distinct(Catalog.category_id)]
listed = db.session.query(Category).join(Catalog).options(contains_eager(Category.products)).filter(Category.id.in_(cat_ids)).all()
newlist = [l.path_to_root()[-1] for l in listed]
new_list = list(set(newlist))


class CategoryList(ResourceList):
    def lists(self):
        cat_ids = [cid for cid, in
                   db.session.query(Catalog.category_id).filter(Catalog.category_id.isnot(None)).distinct(
                       Catalog.category_id)]
        listed = db.session.query(Category).join(Catalog).options(contains_eager(Category.products)).filter(
            Category.id.in_(cat_ids)).all()
        newlist = [l.path_to_root()[-1] for l in listed]
        new_list = list(set(newlist))
        return new_list

    schema = CategorySchema
    data_layer = {'session': db.session,
                  'model': Category,
                  'methods': {'query': lists}}


api.route(CategoryList, 'category_list', '/categories')

# api.route(CategoryDetail, 'category_detail', '/categories/<int:id>', '/computers/<int:computer_id>/owner')
# api.route(PersonRelationship, 'person_computers', '/persons/<int:id>/relationships/computers')

# api.route(PersonList, 'person_list', '/persons')
# api.route(PersonDetail, 'person_detail', '/persons/<int:id>', '/computers/<int:computer_id>/owner')
# api.route(PersonRelationship, 'person_computers', '/persons/<int:id>/relationships/computers')
# api.route(ComputerList, 'computer_list', '/computers', '/persons/<int:id>/computers')
# api.route(ComputerDetail, 'computer_detail', '/computers/<int:id>')
# api.route(ComputerRelationship, 'computer_person', '/computers/<int:id>/relationships/owner')

@main.route('/')
def index():
    return render_template('anchor/index.html')


MongoDB Data Layer

MongoDB data layer is documented but not supported in the current version. Are there any plans to release the MongoDataLayer?

Unique handling SqlAlchemy

Hello,
Thank you for your great work.
I am integrating flask-rest-jsonapi into my project and I have a question for you.
I use Flask-SqlAlchemy and have a model with a "unique" field.

When I try to create an object twice, this is the answer I get:

{ "errors": [ { "detail": "Object creation error: (psycopg2.IntegrityError) duplicate key value violates unique constraint "user_email_key" DETAIL: Key (email)=([email protected]) already exists. [SQL: 'INSERT INTO "user" ("firstName", "lastName", email, password, active, "createdAt", "group") VALUES (%(firstName)s, %(lastName)s, %(email)s, %(password)s, %(active)s, %(createdAt)s, %(group)s) RETURNING "user".id'] [parameters: {'firstName': 'toto', 'lastName': 'toto', 'email': '[email protected]', 'password': '$2b$12$XnZZlBozAcMP12ZnOjsP4.i3ftCOYTNLw9EnbsvZQtRSRYYXlldf6', 'active': False, 'createdAt': datetime.datetime(2017, 11, 8, 14, 39, 49, 244605), 'group': 'S'}]", "source": { "pointer": "/data" }, "status": 500, "title": "Unknown error" } ], "jsonapi": { "version": "1.0" } }

I get the error raised by SqlAlchemy as error description, and a 500 status.
How would it be possible to catch this exception and send a more human-friendly JsonApiException?

Sqlalchemy properties throws error when sorted

Step to reproduce:
When using a hybrid property as follows:

@hybrid_property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email

In raw SQL query, you get order by table.email (the email column doesn't exist in the actual table) instead of order by table._email
A possible fix for this is here: shubham-padia@3c62271
If this seems okay, I can make a PR for it :) .

include multiple related resources

this issues is probably similar to #29 only the suggested answer doesn't solve it.

in a request like this:
api/author/123/?include=articles.comments,articles.ratings

only ratings are included(or only comments, depending on what comes last) never both.

Can backends requirements be optional?

Installing flask-rest-jsonapi will also install pymongo even if I don't use MongoDB. The problem is that pymongo conflicts with other bson implementations which hit me in one project.

Is pymongo a must have for flask-rest-jsonapi to work (without using mongo)?

get_schema_kwargs updates mutably schema_kwargs in get method

schema_kwargs.update({'many': True})

If a user is using a dictionary and passes get_schema_kwargs, the update is mutably resulting in unexpected behaviour because the user might want to user the same dictionary somewhere else.

Example:
My resource classes looks like the following:

class _BaseResource(object):
    _schema_kwargs = {'exclude': ('password',)}
    def _check_authorized():
        ...
    def get_schema_kwargs(self):
        return self._schema_kwargs if not self._check_authorized() else None

class SomeResourceDetail(ResourceDetail, _BaseResource):
    ...
class SomeResourceList(ResourceList, _BaseResource):
    ...

In this case, when the list is first fetched the _schema_kwargs will get updated to {'exclude': ('password',), 'many': True} and when the detail resource is being fetched it will report an error saying that <model of SomeResource> is not iterable.

Successful DELETE not returning propper response

Hi, I think that according to the jsonapi specification (http://jsonapi.org/format/#document-meta), DELETE should return a meta object, but this library is returning a string.
Incorrect:

  "meta": {
    "Object successful deleted"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

Correct:

  "meta": {
    "result": "Object successful deleted"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

I was having problems using this lib until I figured this out.
Cheers.

Unable to pass filter parameters while using oauthlib

Hey. I've picked up an issue while trying to pass filter params while using flask-oauthlib. This is not an issue with flask-rest-jsonapi, but with the oauthlib 2.0.2 library. I am however giving you a heads up as it directly affects the functioning of this package. When trying to pass the following characters in "{}[]" in filter request you will recieve the following error:

127.0.0.1 - - [21/May/2017 07:44:58] "GET /api/products?filter=[{%22name%22:%22owner%22,%22op%22:%22has%22,%22val%22:%20{%22name%22:%22id%22,%22op%22:%22eq%22,%22val%22:1}}] HTTP/1.1
" 500 -
Traceback (most recent call last):
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask_cors\extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\_compat.py", line 33, in reraise
    raise value
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask_cors\extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\_compat.py", line 33, in reraise
    raise value
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask\views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask_rest_jsonapi\resource.py", line 66, in dispatch_request
    raise e
  File "C:\projects\pythonapi\venv\lib\site-packages\flask_rest_jsonapi\resource.py", line 59, in dispatch_request
    response = method(*args, **kwargs)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask_rest_jsonapi\decorators.py", line 32, in wrapped
    return func(*args, **kwargs)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask_oauthlib\provider\oauth2.py", line 555, in decorated
    valid, req = self.verify_request(scopes)
  File "C:\projects\pythonapi\venv\lib\site-packages\flask_oauthlib\provider\oauth2.py", line 483, in verify_request
    uri, http_method, body, headers, scopes
  File "C:\projects\pythonapi\venv\lib\site-packages\oauthlib\oauth2\rfc6749\endpoints\base.py", line 64, in wrapper
    return f(endpoint, uri, *args, **kwargs)
  File "C:\projects\pythonapi\venv\lib\site-packages\oauthlib\oauth2\rfc6749\endpoints\resource.py", line 68, in verify_request
    request = Request(uri, http_method, body, headers)
  File "C:\projects\pythonapi\venv\lib\site-packages\oauthlib\common.py", line 417, in __init__
    self._params.update(dict(urldecode(self.uri_query)))
  File "C:\projects\pythonapi\venv\lib\site-packages\oauthlib\common.py", line 131, in urldecode
    raise ValueError(error % (set(query) - urlencoded, query))
ValueError: Error trying to decode a non urlencoded string. Found invalid characters: {']', '[', '}', '{'} in the string: 'filter=[{%22name%22:%22owner%22,%22op%22:%22has%22,%22val%2
2:%20{%22name%22:%22id%22,%22op%22:%22eq%22,%22val%22:1}}]'. Please ensure the request/response body is x-www-form-urlencoded.

I managed to resolve the issue by modifying oauthlib and have created a pull request here: oauthlib/oauthlib#477 . Anyone wanting to continue development may implement this until it hopefully gets pulled into the master.

creating multiple related urls

I have ran into some errors while registering multiple related schema.

Suppose we have 3 schema like below:

class A_Schema(Schema):
C = Relationship( .... )

class B_Schema(Schema):
C = Relationship( .... )

class C_Schema(Schema):

Now, Issue is how do I register related urls for A.C (/A//C) and B.C (/B//C) . While trying to create both urls, I have been running into over-write view method errors; created multiple resources, but got stuck at some:-
"BuildError: Could not build url for endpoint 'brand_api.company_related_brand'. Did you forget to specify values ['id']? // Werkzeug Debugger"

Sqlalchemy issue when setting multiple relationships in the same call

I've been running into some issues over the last few months setting multiple relationships in the same API call. Here's an example error message I received earlier today trying to set two relationships in the same call:

sqlalchemy.exc.IntegrityError: (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely) (psycopg2.IntegrityError) null value in column "conversation_id" violates not-null constraint

The problem seems to be related to the apply_relationships method in the SqlalchemyDataLayer. As it loops through the relationship fields it tries to fetch the related_object and immediately sets the relationship with setattr(obj, key, related_obj). Although this works for the first relationship, it fails with the above error when it tries to fetch the second related_object.

I am able to get around this problem with the following custom data layer that waits to set the relationships until all of them have been fetched. I'm not sure if this is the "right" way to solve it, but I wanted to post it here in case anyone else finds it helpful or can shed light on a better way.

class ImprovedDataLayer(SqlalchemyDataLayer):

    def apply_relationships(self, data, obj):
        """Apply relationship provided by data to obj
        :param dict data: data provided by the client
        :param DeclarativeMeta obj: the sqlalchemy object to plug relationships to
        :return boolean: True if relationship have changed else False
        """
        relationships_to_apply = []
        relationship_fields = get_relationships(self.resource.schema)
        for key, value in data.items():
            if key in relationship_fields:
                related_model = getattr(obj.__class__, key).property.mapper.class_
                related_id_field = self.resource.schema._declared_fields[
                    relationship_fields[key]].id_field

                if isinstance(value, list):
                    related_objects = []

                    for identifier in value:
                        related_object = self.get_related_object(related_model,
                                                                 related_id_field,
                                                                 {'id': identifier})
                        related_objects.append(related_object)

                    # setattr(obj, key, related_objects)
                    relationships_to_apply.append({
                        'field': key,
                        'value': related_objects
                    })
                else:
                    related_object = None

                    if value is not None:
                        related_object = self.get_related_object(related_model,
                                                                 related_id_field,
                                                                 {'id': value})

                    # setattr(obj, key, related_object)
                    relationships_to_apply.append({
                        'field': key,
                        'value': related_object
                    })

        # Wait to set relationships until all have been fetched from the db
        for relationship in relationships_to_apply:
            setattr(obj, relationship['field'], relationship['value'])

Accept header on GET request

Does not JSONAPI 1.0 says, GET requests be made with Accept header "application/vnd.api+json"; But, I am able to make GET requests without any headers.

Also, is response body in GET according to JSONAPI specs? Our app broke as it was not able to parse response body (It was working fine with restless before migrate).
Or, do I need to put in some options in API library to change mime type to application/vnd.api+json? I thought that to be default.

How to apply a database filter to an included relationship?

My include api request looks like this: http://127.0.0.1:5000/api/products?include=favorites

It's working correctly and includes favorites for products in the payload. However, I'd like to only include favorites for products that the current user has favorited. Where is the right place to set this up? The schema/view/data layer? I've looked through the source code quite a bit and can't seem to find the right way to filter the database query for the included favorites relationship (using SqlAlchemy). Any recommendations on how to approach this?

Support conforming filter syntax

The way flask-rest-jsonapi accepts filter params doesn't match the JSONAPI spec, and therefore makes it really ugly to filter with JSONAPI clients that support it. For example in Ember I have to do:

filter: `[${JSON.stringify({
  name: 'name',
  op: 'eq',
  val: name
})}]`

as opposed to what I can normally do with conforming server implementations:

filter: { name }

You can see why this might not be ideal.

Default PAGE_SIZE not set in __init__

The recent change to using a default PAGE_SIZE from app.config causes a KEY_ERROR if the PAGE_SIZE config option is not set, as it is not by default when initializing an API object from an existing Flask app (as is done in most of the documentation).

The simplest fix seems to be to ensure the default page size is set in init in addition to init_app() of the Api class, although the tests and documentation seem split about the preferred way to construct the Api object.

MongoDataLayer

When instantiating a MongoDataLayer connection the following call in resource.py
object_count, objects = self.data_layer.get_collection(qs, **kwargs)

passes a querystring

this method has no params in mongo.py

{
"errors": [
{
"status": 500,
"source": "",
"detail": "get_collection() takes exactly 1 argument (2 given)",
"title": "Unknow error"
}
]
}

Swagger Documentation?

Are there any plans to implement Swagger integration so we can easily generate API documentation?

pagination.py - 'data' is a list but accessed as a dict?

When i make an API call to my flask app running this library, i get the following error:

File "/home/nate/.virtualenvs/flask-insure/lib/python3.5/site-packages/flask_rest_jsonapi/pagination.py", line 53, in add_pagination_links
data['links'] = links
TypeError: list indices must be integers or slices, not str

When i debug my app, I see that data (passed from ResourceView.get())is a list of objects, but then the line in question is accessing the list as if it were a dictionary. I followed the documentation as closely as possible and my setup so far is extremely bare-bones (see here). Can you see any issues in my setup, and if not can you think of any other reason this is happening?

Thanks for any help you can provide, and thanks for creating such a cool library with such thorough and detailed documentation!

Pass data to before_patch and before_post

Currently, received data (json_data from http://flask-restless.readthedocs.io/en/stable/customizing.html#request-preprocessors-and-postprocessors)
is not passed to before_patch and before_post, thus modifying the incoming data is not a possibility.
flask-restlless currently supports this,

def post_preprocessor(data=None, **kw):
    """Accepts a single argument, `data`, which is the dictionary of
    fields to set on the new instance of the model.

    """
    pass

Ref: http://flask-restless.readthedocs.io/en/stable/customizing.html#request-preprocessors-and-postprocessors

how do you override Content-Type / Accept headers?

I am trying to write an API receiver, where I can not control the other end. The other end only allows headers 'Accept: application/json' and 'Content-Type: application/json'

What is the fastest way to accept and return application/json in the headers?

line 55 in flask_rest_jsonapi.data_layers.filtering.alchemy.py never reached

    def resolve(self): 
        if 'or' not in self.filter_ and 'and' not in self.filter_: 
            if self.val is None and self.field is None: 
                raise InvalidFilters("Can't find value or field in a filter") 
 
            value = self.value 
 
            if isinstance(self.val, dict): 
                value = Node(self.related_model, self.val, self.resource, self.related_schema).resolve() 
 
            if '__' in self.filter_.get('name', ''): 
                value = {self.filter_['name'].split('__')[1]: value} 
 
            if isinstance(value, dict): 
                return getattr(self.column, self.operator)(**value) 
            else: 
                print(getattr(self.column, self.operator)) 
                return getattr(self.column, self.operator)(value) 
 
        if 'or' in self.filter_: 
            return or_(Node(self.model, filt, self.resource, self.schema).resolve() for filt in self.filter_['or']) 
        if 'and' in self.filter_: 
            return and_(Node(self.model, filt, self.resource, self.schema).resolve() for filt in self.filter_['and']) 
        if 'not' in self.filter_: 
            return not_(Node(self.model, self.filter_['not'], self.resource, self.schema).resolve())

If if 'or' not in self.filter_ and 'and' not in self.filter_ is true, you never leave that block. If it's not, or_(Node... or and_(Node... gets called, but never the last not_(Node...

Source tarball

In order to include Flask-REST-JSONAPI in the Debian distribution, please publish a source tarball on PyPI. Please make sure it contains the license, all documentation and tests and ideally sign it with a verifiable GPG key.

Related relationships links returned incorrectly in example app

I can provide the following steps to reproduce, but I have not been able to understand yet what's causing the issue, so I wanted to open this ticket to flag it.

  1. Run the example api.py.
  2. Post a new object to /persons:
{
  "data": {
    "type": "person",
    "attributes": {
      "name": "Person1"
    }
  }
}
  1. Get the person's (empty) relationships object at /persons/1/relationships/computers
{
  "links": {
    "self": "/persons/1/relationships/computers",
    "related": "/persons/1/computers"
  },
  "data": [],
  "jsonapi": {
    "version": "1.0"
  }
}
  1. Post a new object to /persons
{
  "data": {
    "type": "person",
    "attributes": {
      "name": "Person2"
    }
  }
}
  1. Observe that the "relationships" -> "computers" -> "links" -> "related" url is incorrect (refers to person 1). That appears to stick through other permutations (subsequent gets), but depends on having queried previously for another person's relationships.
{
  "data": {
    "type": "person",
    "id": "2",
    "attributes": {
      "display_name": "PERSON2 <None>",
      "birth_date": null
    },
    "relationships": {
      "computers": {
        "links": {
          "self": "/persons/2/relationships/computers",
          "related": "/persons/1/computers"
        }
      }
    },
    "links": {
      "self": "/persons/2"
    }
  },
  "links": {
    "self": "/persons/2"
  },
  "jsonapi": {
    "version": "1.0"
  }
}

I have not investigated deeply enough to understand why this is happening (or tested outside of the example sqlite), but wanted to open the issue in hopes the author can identify the problem since it definitely doesn't seem to match the JSONAPI spec or desired behavior.

Renaming columns in model

I'm rewriting an old application where tables and columns names are in a foreign language. I would like to translate all to English as soon as possible in the code and limit use of non-english in the code. I also want to fix some inconsistencies in columns names.

First idea was to transform:

class User(db.Model):
    __tablename__ = 'users'
    userid = db.Column(db.Integer, primary_key=True)
    user = db.Column(db.String)

to

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column('userid', db.Integer, primary_key=True)
    name = db.Column(db.String)

but it raise: Exception: User has no attribute userid

Adding key='id' has no effect. (id = db.Column('userid', db.Integer, primary_key=True, key='id'))

I'll propose a PR for that.

Support for tornado?

Hello,

I need a library like flask-rest-jsonapi, but using tornado instead. In your opinion, how hard would it be for me to port it to tornado? From the code it seems relatively straightforward.
Alternatively, are you aware of any package providing a similar API and technical level, but for tornado?

Thanks

With the include parameter, do not pick the ones that do not have date in the included

I do not know if it is a bad configuration of mine or a Json specification. My problem is that I have a table of ads and would like to bring the photos and categories of the ads together in the ad endpoint. When placing the include parameter of photos and categories the result only brings those that have photos and categories and ignores the ones that do not have. Is it a JsonApi standard or can it be a bad setup in my API?

Sorry for grammatical errors, English is not my native language.

Accessing OAuth protected resources with bearer token

Hi, can someone help me designing requests with bearer token which I got from authorization endpoint from my Auth provider to access flask-rest-jsonapi resources which I have protected with oauth. I have followed the specs to the document.
When I put a disable_oauth = True in resource class I get the access to resources as before; which makes me believe my oauth setup is correct for flask-rest-jsonapi. I am following RFC 6750 for building my requests. Any assistance on how to form requests would be great. Also, a demo/skeleton for putting the resources inside oauth decorators would be good, but I guess that has been covered in docs and I'm following them by word.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.