Skip to content

Commit

Permalink
Merge pull request #48 from AdCombo/develop
Browse files Browse the repository at this point in the history
Update api, api_nested examples, update docs
  • Loading branch information
Znbiz authored May 29, 2021
2 parents b6303f3 + fc9a549 commit bb8472b
Show file tree
Hide file tree
Showing 75 changed files with 1,918 additions and 960 deletions.
55 changes: 43 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
.. image:: https://github.com/AdCombo/flask-combo-jsonapi/workflows/Python%20tests%20and%20coverage/badge.svg
:target: https://github.com/AdCombo/flask-combo-jsonapi/actions
:alt: flask-combo-jsonapi actions
:target: https://github.com/AdCombo/flask-combo-jsonapi/actions
.. image:: https://coveralls.io/repos/github/AdCombo/flask-combo-jsonapi/badge.svg
:target: https://coveralls.io/github/AdCombo/flask-combo-jsonapi
:alt: flask-combo-jsonapi coverage
:target: https://coveralls.io/github/AdCombo/flask-combo-jsonapi
.. image:: https://img.shields.io/pypi/v/flask-combo-jsonapi.svg
:alt: PyPI
:target: http://pypi.org/p/flask-combo-jsonapi


Flask-COMBO-JSONAPI
###################

Flask-COMBO-JSONAPI is a flask extension for building REST APIs. It combines the power of `Flask-Restless <https://flask-restless.readthedocs.io/>`_ and the flexibility of `Flask-RESTful <https://flask-restful.readthedocs.io/>`_ around a strong specification `JSONAPI 1.0 <http://jsonapi.org/>`_. This framework is designed to quickly build REST APIs and fit the complexity of real life projects with legacy data and multiple data storages.

The main goal is to make it flexible using `plugin system <https://github.com/AdCombo/combojsonapi/blob/develop/docs/en/create_plugins.rst>`_
The main goal is to make it flexible using `plugin system <https://combojsonapi.readthedocs.io/>`_


Install
=======

pip install Flask-COMBO-JSONAPI

Installation from pypi is not ready yet. Refer to the `installation manual <https://github.com/AdCombo/flask-combo-jsonapi/blob/develop/docs/installation.rst/>`_


A minimal API
=============
Expand All @@ -28,23 +31,27 @@ A minimal API
from flask import Flask
from flask_combo_jsonapi import Api, ResourceDetail, ResourceList
from flask_sqlalchemy import SQLAlchemy
from marshmallow import pre_load
from marshmallow_jsonapi.flask import Schema
from marshmallow_jsonapi import fields
# Create the Flask application and the Flask-SQLAlchemy object.
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/api_minimal.db'
db = SQLAlchemy(app)
# Create model
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
# Create the database.
db.create_all()
# Create schema
class PersonSchema(Schema):
class Meta:
Expand All @@ -54,18 +61,37 @@ A minimal API
self_view_many = 'person_list'
id = fields.Integer(as_string=True, dump_only=True)
name = fields.Str()
name = fields.String()
@pre_load
def remove_id_before_deserializing(self, data, **kwargs):
"""
We don't want to allow editing ID on POST / PATCH
Related issues:
https://github.com/AdCombo/flask-combo-jsonapi/issues/34
https://github.com/miLibris/flask-rest-jsonapi/issues/193
"""
if 'id' in data:
del data['id']
return data
# Create resource managers
class PersonList(ResourceList):
schema = PersonSchema
data_layer = {'session': db.session,
'model': Person}
data_layer = {
'session': db.session,
'model': Person,
}
class PersonDetail(ResourceDetail):
schema = PersonSchema
data_layer = {'session': db.session,
'model': Person}
data_layer = {
'session': db.session,
'model': Person,
}
# Create the API object
api = Api(app)
Expand All @@ -76,6 +102,7 @@ A minimal API
if __name__ == '__main__':
app.run()
This example provides the following API structure:

======================== ====== ============= ===========================
Expand All @@ -88,6 +115,10 @@ URL method endpoint Usage
/persons/<int:person_id> DELETE person_detail Delete a person
======================== ====== ============= ===========================


`More detailed example in the docs <https://flask-combo-jsonapi.readthedocs.io/en/stable/minimal_api_example.html>`_


Flask-COMBO-JSONAPI vs `Flask-RESTful <https://flask-restful.readthedocs.io/en/latest/>`_
==========================================================================================

Expand All @@ -107,7 +138,7 @@ Flask-COMBO-JSONAPI vs `Flask-Restless <https://flask-restless.readthedocs.io/en
Documentation
=============

Documentation available here: http://Flask-COMBO-JSONAPI.readthedocs.io/en/latest/
Documentation available here: https://flask-combo-jsonapi.readthedocs.io/

Thanks
======
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
# The short X.Y version.
version = "1.0"
# The full version, including alpha/beta/rc tags.
release = "1.0.6"
release = "1.0.7"

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
4 changes: 2 additions & 2 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Configuration
You have access to 5 configration keys:

* PAGE_SIZE: the number of items in a page (default is 30)
* MAX_PAGE_SIZE: the maximum page size. If you specify a page size greater than this value you will receive 400 Bad Request response.
* MAX_PAGE_SIZE: the maximum page size. If you specify a page size greater than this value you will receive a 400 Bad Request response.
* MAX_INCLUDE_DEPTH: the maximum length of an include through schema relationships
* ALLOW_DISABLE_PAGINATION: if you want to disallow to disable pagination you can set this configuration key to False
* CATCH_EXCEPTIONS: if you want flask_combo_jsonapi to catch all exceptions and return as JsonApiException (default is True)
* CATCH_EXCEPTIONS: if you want flask_combo_jsonapi to catch all exceptions and return them as JsonApiException (default is True)
22 changes: 13 additions & 9 deletions docs/data_layer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ Data layer

.. currentmodule:: flask_combo_jsonapi

| The data layer is a CRUD interface between resource manager and data. It is a very flexible system to use any ORM or data storage. You can even create a data layer that use multiple ORMs and data storage to manage your own objects. The data layer implements a CRUD interface for objects and relationships. It also manage pagination, filtering and sorting.
| The data layer is a CRUD interface between resource manager and data. It is a very flexible system to use any ORM or data storage. You can even create a data layer that uses multiple ORMs and data storage to manage your own objects. The data layer implements a CRUD interface for objects and relationships. It also manages pagination, filtering and sorting.
|
| Flask-COMBO-JSONAPI has a full featured data layer that use the popular ORM `SQLAlchemy <https://www.sqlalchemy.org/>`_.
| Flask-COMBO-JSONAPI has a full-featured data layer that uses the popular ORM `SQLAlchemy <https://www.sqlalchemy.org/>`_.
.. note::

The default data layer used by a resource manager is the SQLAlchemy one. So if you want to use it, you don't have to specify the class of the data layer in the resource manager
The default data layer used by a resource manager is the SQLAlchemy one. So if that's what you want to use, you don't have to specify the class of the data layer in the resource manager

To configure the data layer you have to set its required parameters in the resource manager.

Expand All @@ -28,11 +28,13 @@ Example:
data_layer = {'session': db.session,
'model': Person}
You can also plug additional methods to your data layer in the resource manager. There are two kinds of additional methods:
You can also plug additional methods into your data layer in the resource manager. There are two kinds of additional methods:

* query: the "query" additional method takes view_kwargs as parameter and return an alternative query to retrieve the collection of objects in the GET method of the ResourceList manager.
* query: the "query" additional method takes view_kwargs as parameter and returns an alternative query to retrieve the collection of objects in the GET method of the ResourceList manager.

* pre / post process methods: all CRUD and relationship(s) operations have a pre / post process methods. Thanks to it you can make additional work before and after each operations of the data layer. Parameters of each pre / post process methods are available in the `flask_combo_jsonapi.data_layers.base.Base <https://github.com/AdCombo/flask-combo-jsonapi/blob/master/flask_combo_jsonapi/data_layers/base.py>`_ base class.
* pre-/postprocess methods: all CRUD and relationship(s) operations have pre-/postprocess methods.
Thanks to these you can do additional work before and after each operation of the data layer.
Parameters of each pre-/postprocess method are available in the `flask_combo_jsonapi.data_layers.base.Base <https://github.com/AdCombo/flask-combo-jsonapi/blob/master/flask_combo_jsonapi/data_layers/base.py>`_ base class.

Example:

Expand Down Expand Up @@ -68,7 +70,7 @@ Example:
.. note::

You don't have to declare additional data layer methods in the resource manager. You can declare them in a dedicated module or in the declaration of the model.
You don't have to declare additional data layer methods in the resource manager. You can declare them in a dedicated module or in the model's declaration.

Example:

Expand Down Expand Up @@ -100,12 +102,14 @@ Optional parameters:
:id_field: the field used as identifier field instead of the primary key of the model
:url_field: the name of the parameter in the route to get value to filter with. Instead "id" is used.

By default SQLAlchemy eagerload related data specified in include querystring parameter. If you want to disable this feature you must add eagerload_includes: False to data layer parameters.
By default SQLAlchemy eagerly loads related data specified in the include query string parameter. If you want to disable this feature you must add eagerload_includes: False to the data layer parameters.

Custom data layer
-----------------

Like I said previously you can create and use your own data layer. A custom data layer must inherit from `flask_combo_jsonapi.data_layers.base.Base <https://github.com/AdCombo/flask-combo-jsonapi/blob/master/flask_combo_jsonapi/data_layers/base.py>`_. You can see the full scope of possibilities of a data layer in this base class.
As previously mentioned, you can create and use your own data layer.
A custom data layer must inherit from `flask_combo_jsonapi.data_layers.base.Base <https://github.com/AdCombo/flask-combo-jsonapi/blob/master/flask_combo_jsonapi/data_layers/base.py>`_.
You can see the full scope of possibilities of a data layer in this base class.

Usage example:

Expand Down
12 changes: 6 additions & 6 deletions docs/errors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Errors

.. currentmodule:: flask_combo_jsonapi

JSONAPI 1.0 specification recommand to return errors like that:
The JSON:API 1.0 specification recommends to return errors like this:

.. sourcecode:: http

Expand All @@ -28,9 +28,9 @@ JSONAPI 1.0 specification recommand to return errors like that:
}
}

The "source" field gives information about the error if it is located in data provided or in querystring parameter.
The "source" field gives information about the error if it is located in data provided or in a query string parameter.

The previous example displays error located in data provided instead of this next example displays error located in querystring parameter "include":
The previous example shows an error located in data provided. The following example shows error in the query string parameter "include":

.. sourcecode:: http

Expand All @@ -53,13 +53,13 @@ The previous example displays error located in data provided instead of this nex
}
}

Flask-COMBO-JSONAPI provides two kind of helpers to achieve error displaying:
Flask-COMBO-JSONAPI provides two kinds of helpers for displaying errors:

| * **the errors module**: you can import jsonapi_errors from the `errors module <https://github.com/AdCombo/flask-combo-jsonapi/blob/master/flask_combo_jsonapi/errors.py>`_ to create the structure of a list of errors according to JSONAPI 1.0 specification
|
| * **the exceptions module**: you can import lot of exceptions from this `module <https://github.com/AdCombo/flask-combo-jsonapi/blob/master/flask_combo_jsonapi/exceptions.py>`_ that helps you to raise exceptions that will be well formatted according to JSONAPI 1.0 specification
| * **the exceptions module**: you can import a lot of exceptions from this `module <https://github.com/AdCombo/flask-combo-jsonapi/blob/master/flask_combo_jsonapi/exceptions.py>`_ that helps you to raise exceptions that will be well-formatted according to the JSON:API 1.0 specification
When you create custom code for your api I recommand to use exceptions from exceptions module of Flask-COMBO-JSONAPI to raise errors because JsonApiException based exceptions are catched and rendered according to JSONAPI 1.0 specification.
When you create custom code for your API I recommand using exceptions from the Flask-COMBO-JSONAPI's exceptions module to raise errors because JsonApiException-based exceptions are caught and rendered according to the JSON:API 1.0 specification.

Example:

Expand Down
71 changes: 38 additions & 33 deletions docs/filtering.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ Filtering

.. currentmodule:: flask_combo_jsonapi

Flask-COMBO-JSONAPI as a very flexible filtering system. The filtering system is completely related to the data layer used by the ResourceList manager. I will explain the filtering interface for SQLAlchemy data layer but you can use the same interface to your filtering implementation of your custom data layer. The only requirement is that you have to use the "filter" querystring parameter to make filtering according to the JSONAPI 1.0 specification.
Flask-COMBO-JSONAPI has a very flexible filtering system.
The filtering system is directly attached to the data layer used by the ResourceList manager.
These examples show the filtering interface for SQLAlchemy's data layer
but you can use the same interface for your custom data layer's filtering implementation as well.
The only requirement is that you have to use the "filter" query string parameter
to filter according to the JSON:API 1.0 specification.

.. note::

Expand All @@ -14,19 +19,19 @@ Flask-COMBO-JSONAPI as a very flexible filtering system. The filtering system is
SQLAlchemy
----------

The filtering system of SQLAlchemy data layer has exactly the same interface as the filtering system of `Flask-Restless <https://flask-restless.readthedocs.io/en/stable/searchformat.html#query-format>`_.
The filtering system of SQLAlchemy's data layer has exactly the same interface as the one used in `Flask-Restless <https://flask-restless.readthedocs.io/en/stable/searchformat.html#query-format>`_.
So this is a first example:

.. sourcecode:: http

GET /persons?filter=[{"name":"name","op":"eq","val":"John"}] HTTP/1.1
Accept: application/vnd.api+json

In this example we want to retrieve persons which name is John. So we can see that the filtering interface completely fit the filtering interface of SQLAlchemy: a list a filter information.
In this example we want to retrieve person records for people named John. So we can see that the filtering interface completely fits that of SQLAlchemy: a list a filter information.

:name: the name of the field you want to filter on
:op: the operation you want to use (all sqlalchemy operations are available)
:val: the value that you want to compare. You can replace this by "field" if you want to compare against the value of an other field
:op: the operation you want to use (all SQLAlchemy operations are available)
:val: the value that you want to compare. You can replace this by "field" if you want to compare against the value of another field

Example with field:

Expand All @@ -35,7 +40,7 @@ Example with field:
GET /persons?filter=[{"name":"name","op":"eq","field":"birth_date"}] HTTP/1.1
Accept: application/vnd.api+json

In this example, we want to retrieve persons that name is equal to his birth_date. I know, this example is absurd but it is just to explain the syntax of this kind of filter.
In this example, we want to retrieve people whose name is equal to their birth_date. This example is absurd, it's just here to explain the syntax of this kind of filter.

If you want to filter through relationships you can do that:

Expand All @@ -56,9 +61,9 @@ If you want to filter through relationships you can do that:

.. note ::
When you filter on relationships use "any" operator for "to many" relationships and "has" operator for "to one" relationships.
When you filter on relationships use the "any" operator for "to many" relationships and the "has" operator for "to one" relationships.
There is a shortcut to achieve the same filter:
There is a shortcut to achieve the same filtering:

.. sourcecode:: http

Expand Down Expand Up @@ -107,36 +112,36 @@ You can also use boolean combination of operations:

Common available operators:

* any: used to filter on to many relationships
* any: used to filter on "to many" relationships
* between: used to filter a field between two values
* endswith: check if field ends with a string
* eq: check if field is equal to something
* ge: check if field is greater than or equal to something
* gt: check if field is greater than to something
* has: used to filter on to one relationships
* ilike: check if field contains a string (case insensitive)
* in\_: check if field is in a list of values
* is\_: check if field is a value
* isnot: check if field is not a value
* like: check if field contains a string
* le: check if field is less than or equal to something
* lt: check if field is less than to something
* match: check if field match against a string or pattern
* ne: check if field is not equal to something
* notilike: check if field does not contains a string (case insensitive)
* notin\_: check if field is not in a list of values
* notlike: check if field does not contains a string
* startswith: check if field starts with a string
* endswith: checks if field ends with a string
* eq: checks if field is equal to something
* ge: checks if field is greater than or equal to something
* gt: checks if field is greater than something
* has: used to filter on "to one" relationships
* ilike: checks if field contains a string (case insensitive)
* in\_: checks if field is in a list of values
* is\_: checks if field is a value
* isnot: checks if field is not a value
* like: checks if field contains a string
* le: checks if field is less than or equal to something
* lt: checks if field is less than something
* match: checks if field matches against a string or pattern
* ne: checks if field is not equal to something
* notilike: checks if field does not contain a string (case insensitive)
* notin\_: checks if field is not in a list of values
* notlike: checks if field does not contain a string
* startswith: checks if field starts with a string

.. note::

Availables operators depend on field type in your model
Available operators depend on the field type in your model

Simple filters
--------------

Simple filter adds support for a simplified form of filters and supports only *eq* operator.
Each simple filter transforms to original filter and appends to list of filters.
Simple filters add support for a simplified form of filters and support only the *eq* operator.
Each simple filter is transformed into a full filter and appended to the list of filters.

For example

Expand All @@ -145,22 +150,22 @@ For example
GET /persons?filter[name]=John HTTP/1.1
Accept: application/vnd.api+json

equals to:
equals:

.. sourcecode:: http

GET /persons?filter[name]=[{"name":"name","op":"eq","val":"John"}] HTTP/1.1
Accept: application/vnd.api+json


You can also use more than one simple filter in request:
You can also use more than one simple filter in a request:

.. sourcecode:: http

GET /persons?filter[name]=John&filter[gender]=male HTTP/1.1
Accept: application/vnd.api+json

which equals to:
which is equal to:

.. sourcecode:: http

Expand Down
Loading

0 comments on commit bb8472b

Please sign in to comment.