Restful API framework wrapped around MongoEngine

Overview

Flask-MongoRest Build Status

A Restful API framework wrapped around MongoEngine.

Setup

from flask import Flask
from flask_mongoengine import MongoEngine
from flask_mongorest import MongoRest
from flask_mongorest.views import ResourceView
from flask_mongorest.resources import Resource
from flask_mongorest import operators as ops
from flask_mongorest import methods


app = Flask(__name__)

app.config.update(
    MONGODB_HOST = 'localhost',
    MONGODB_PORT = '27017',
    MONGODB_DB = 'mongorest_example_app',
)

db = MongoEngine(app)
api = MongoRest(app)

class User(db.Document):
    email = db.EmailField(unique=True, required=True)

class Content(db.EmbeddedDocument):
    text = db.StringField()

class ContentResource(Resource):
    document = Content

class Post(db.Document):
    title = db.StringField(max_length=120, required=True)
    author = db.ReferenceField(User)
    content = db.EmbeddedDocumentField(Content)

class PostResource(Resource):
    document = Post
    related_resources = {
        'content': ContentResource,
    }
    filters = {
        'title': [ops.Exact, ops.Startswith],
        'author_id': [ops.Exact],
    }
    rename_fields = {
        'author': 'author_id',
    }

@api.register(name='posts', url='/posts/')
class PostView(ResourceView):
    resource = PostResource
    methods = [methods.Create, methods.Update, methods.Fetch, methods.List]

With this app, following cURL commands could be used:

Create a Post:
curl -H "Content-Type: application/json" -X POST -d \
'{"title": "First post!", "author_id": "author_id_from_a_previous_api_call", "content": {"text": "this is our test post content"}}' http://0.0.0.0:5000/posts/
{
  "id": "1",
  "title": "First post!",
  "author_id": "author_id_from_a_previous_api_call",
  "content": {
    "text": "this is our test post content"
  }
} 

Get a Post:

curl http://0.0.0.0:5000/posts/1/
{
  "id": "1",
  "title": "First post!",
  "author_id": "author_id_from_a_previous_api_call",
  "content": {
    "text": "this is our test post content"
  }
} 

List all Posts or filter by the title:

curl http://0.0.0.0:5000/posts/ or curl http://0.0.0.0:5000/posts/?title__startswith=First%20post
{
  "data": [
    {
      "id": "1",
      "title": "First post!",
      "author_id": "author_id_from_a_previous_api_call",
      "content": {
        "text": "this is our test post content"
      }
    },
    ... other posts
  ]
}

Delete a Post:

curl -X DELETE http://0.0.0.0:5000/posts/1/
# Fails since PostView.methods does not allow Delete

Request Params

_skip and _limit => utilize the built-in functions of mongodb.

_fields => limit the response's fields to those named here (comma separated).

_order_by => order results if this string is present in the Resource.allowed_ordering list.

Resource Configuration

rename_fields => dict of renaming rules. Useful for mapping _id fields such as "organization": "organization_id"

filters => filter results of a List request using the allowed filters which are used like /user/?id__gt=2 or /user/[email protected]

related_resources => nested resource serialization for reference/embedded fields of a document

child_document_resources => Suppose you have a Person base class which has Male and Female subclasses. These subclasses and their respective resources share the same MongoDB collection, but have different fields and serialization characteristics. This dictionary allows you to map class instances to their respective resources to be used during serialization.

Authentication

The AuthenticationBase class provides the ability for application's to implement their own API auth. Two common patterns are shown below along with a BaseResourceView which can be used as the parent View of all of your app's resources.

class SessionAuthentication(AuthenticationBase):
    def authorized(self):
        return current_user.is_authenticated()

class ApiKeyAuthentication(AuthenticationBase):
    """
    @TODO ApiKey document and key generation left to the specific implementation
    """
    def authorized(self):
        if 'AUTHORIZATION' in request.headers:
            authorization = request.headers['AUTHORIZATION'].split()
            if len(authorization) == 2 and authorization[0].lower() == 'basic':
                try:
                    authorization_parts = base64.b64decode(authorization[1]).partition(':')
                    key = smart_unicode(authorization_parts[0])
                    api_key = ApiKey.objects.get(key__exact=key)
                    if api_key.user:
                        login_user(api_key.user)
                        setattr(current_user, 'api_key', api_key)
                    return True
                except (TypeError, UnicodeDecodeError, ApiKey.DoesNotExist):
                    pass
        return False

class BaseResourceView(ResourceView):
    authentication_methods = [SessionAuthentication, ApiKeyAuthentication]

Running the test suite

This package uses nosetests for automated testing. Just run python setup.py nosetests to run the tests. No setup or any other prep needed.

Contributing

Pull requests are greatly appreciated!

Comments
  • Dealing with embedded documents.

    Dealing with embedded documents.

    Hey there,

    I'm new to MongoDB and I would like to know how I can access an embedded document using flask-mongorest. I know how to do it in the cli, but I can't seem to find documentation here.

    Example

    Given an output of...

    
        "data": [
            {
                "adducts": {
                    "Anion": {
                        "[M-H]1-": [
                            [
                                349.2093240735, 
                                100.0
                            ], 
                            [
                                350.2126789113, 
                                21.631456585464488
                            ]
                        ]
                    }, 
                    "Canion": {}, 
                    "Nominal": [
                        [
                            350.2093240735, 
                            100.0
                        ], 
                        [
                            351.2126789113, 
                            21.631456585464488
                        ]
                    ]
                }, 
                "id": "586bf20b9f0029837dfc9d39", 
                "molecular_formula": "C20H30O5", 
                "name": "Oryzalic acid B", 
                "origins": [
                    "Endogenous", 
                    "Food"
                ]
            }...
    

    I'd like to filter out anything that has an "anion" from "adducts" from a given value compared to the first element the first list in that given key.

    Is this possible in flask-mongorest?

    Thanks,

    Keiron.

    opened by KeironO 24
  • Doesn't this violate REST principles?

    Doesn't this violate REST principles?

    Maybe I'm missing something, but I think there is a fundamental issue in MongoRest.

    I created a super simple example to demonstrate the issue here: https://gist.github.com/mtiller/4961630

    If I run this application and then create an author as follows:

    % curl -H "Content-Type: application/json" -X POST -d '{"name": "Douglas Adams"}' http://localhost:5000/authors/

    It creates an author, but the response looks like this:

    {"name": "Douglas Adams", "id": "511e66731d41c8718c196708"}

    The problem I see here is that this is not returning a URI. The "Uniform Interface" constraint for REST says that resources should be named. In this case, the resources name is, in fact, /authors/511e66731d41c8718c196708/ (which I figured out by trial and error). But a POST should return to the URI (resource), not the representation. If I had done this:

    % curl http://localhost:5000/authors/511e66731d41c8718c196708/

    THEN, I get:

    {"name": "Douglas Adams", "id": "511e66731d41c8718c196708"}

    ...which is correct since this is a JSON representation.

    But the problem goes deeper than just POST responses. If I then want to create a Book object I should be using the RESOURCE not the representation, e.g.

    curl -H "Content-Type: application/json" -X POST -d '{"title": "Hitchhikers Guide to the Galaxy", "author": "/authors/511e66731d41c8718c196708/"}' http://localhost:5000/books/

    However, this fails with:

    {"field-errors": {"name": "Field is required"}}

    It turns out what is required is this:

    % curl -H "Content-Type: application/json" -X POST -d '{"title": "Hitchhikers Guide to the Galaxy", "author": {"name": "Douglas Adams", "id": "511e66731d41c8718c196708"}}' http://localhost:5000/books/

    Note that I had to put the REPRESENTATION in for the author, not the resource.

    Am I missing something here? This seems like a significantly violation of REST principles. If I remove the 'related_resources' setting, it gets slightly better because then it requires this:

    % curl -H "Content-Type: application/json" -X POST -d '{"title": "Hitchhikers Guide to the Galaxy", "author": "511e66731d41c8718c196708"}' http://localhost:5000/books/

    ...and you get back...

    {"title": "Hitchhikers Guide to the Galaxy", "id": "511e6bb61d41c8723fba687c", "author": "511e66731d41c8718c196708"}

    So at least now we are giving and getting a resource identifier (although it isn't technically a URI). But it is still inconsistent with what is returned by the POST method used to create the author. In other words, as a developer using such an API I have to understand how to turn the representation (from POST) into a resource identifier which I should not have to do.

    Or am I missing something?

    opened by xogeny 24
  • Bulk update limit

    Bulk update limit

    Primarily to minimize the effects of a poorly constructed request.

    After this change, flask-mongorest will by default limit bulk updates to 1k documents. If more than that would be affected, a 400 response is returned.

    This PR also introduces a method which you can use to validate the request before processing of a bulk update starts.

    opened by wojcikstefan 11
  • How to use Flask-MongoRest resource filters?

    How to use Flask-MongoRest resource filters?

    Following up from https://github.com/closeio/flask-mongorest/issues/103, I am experiencing an issue involving the use of an EmbeddedDocument.

    My MongoDB collection currently resembles the following:

    [
        {
            "accurate_mass": 350.45000749099137, 
            "smiles": "CC(C)(C1CCC23CC(CCC2C1(C)CC(O)=O)C(=C)C3O)C(O)=O", 
            "isotopic_distributions": [
                [
                    0.0, 
                    100.0
                ], 
                [
                    1.003354837799975, 
                    21.631456585464488
                ]
            ], 
            "name": "Oryzalic acid B", 
            "origins": [
                "Endogenous", 
                "Food"
            ], 
            "molecular_formula": "C20H30O5", 
            "adduct_weights": {
                "positive": {
                    "count": 0, 
                    "peaks": []
                }, 
                "neutral": 350.2093240735, 
                "negative": {
                    "count": 1, 
                    "peaks": [
                        [
                            "[M-H]1-", 
                            349.2093240735
                        ]
                    ]
                }
            }
        },...
    ]
    

    If you look at the adduct_weights key, it holds a collection resembling:

    "adduct_weights": {
                "positive": {
                    "count": 0, 
                    "peaks": []
                }, 
                "neutral": 350.2093240735, 
                "negative": {
                    "count": 1, 
                    "peaks": [
                        [
                            "[M-H]1-", 
                            349.2093240735
                        ]
                    ]
                }
    

    Following the example provided within this repository, I have written the following Documents and Resources.

    class NegativeAdduct(db.EmbeddedDocument):
        count = db.IntField()
        peaks = db.ListField(db.ListField(db.DynamicField()))
    
    class PositiveAdduct(db.EmbeddedDocument):
        count = db.IntField()
        peaks = db.ListField(db.ListField(db.DynamicField()))
    
    class AdductWeights(db.EmbeddedDocument):
        neutral = db.FloatField()
        negative = db.EmbeddedDocumentField(NegativeAdduct)
        positive = db.EmbeddedDocumentField(PositiveAdduct)
    
    class AdductWeightsResource(Resource):
        document = AdductWeights
    
    class MetaboliteAdduct(db.DynamicDocument):
        meta = {"collection": "metabolites"}
        name = db.StringField()
        accurate_mass = db.FloatField()
        smiles = db.StringField()
        isotopic_distributions = db.StringField()
        molecular_formula = db.StringField()
        origins = db.ListField(db.StringField())
        adduct_weights = db.EmbeddedDocumentField(AdductWeights)
    
    class MetaboliteAdductResource(Resource):
        document = MetaboliteAdduct
        filters = {
            "name" : [ops.Contains, ops.Startswith, ops.Exact],
        }
    
        related_resources = {
            "adduct_weights" : AdductWeightsResource
        }
    
    @api.register(name="adductsapi", url="/api/adducts/")
    class MetaboliteAdductView(ResourceView):
        resource =  MetaboliteAdductResource
        methods = [methods.List, methods.Fetch]
    

    No error is being thrown when I query MetaboliteAdductView's url, however no data is being returned either.

    {
        "data": [], 
        "has_more": true
    }
    

    Where have I gone wrong here?

    opened by KeironO 9
  • Bulk update improvements:

    Bulk update improvements:

    • Introduces two helper methods (update_objects and update_object) that can be overridden in subclasses
    • Makes sure view_method is propagated to subresources
    • Fixes an issue where has_change_permission is called after the object is updated in bulk updates
    • Fixes an issue where obj.save() is unnecessarily called after update_object() (which already saves) in bulk updates
    opened by thomasst 7
  • Added supplemental validation capability

    Added supplemental validation capability

    These changes allow a developer to define additional custom validation criteria for a Resource by overriding the default custom_validation method on Resource and throwing a ValidationError.

    In addition to adding the feature, I also added test cases to exercise this validation in the context of either a PUT or POST request.

    opened by xogeny 7
  • `params` doesn't make sense if we don't have a request

    `params` doesn't make sense if we don't have a request

    If we don't have an active request context, evaluating params doesn't make sense and is analogous to the attribute not existing at all, so we raise an AttributeError to make hasattr return False.

    opened by jpmelos 6
  • Python 3 incompatible syntax on views.py

    Python 3 incompatible syntax on views.py

    There are a couple of except Exception, e: in views.py and that syntax is incompatible with python 3. A simple change to except Exception as e: should solve the issue.

    opened by marco-lavagnino 4
  • Form Validation of None Values

    Form Validation of None Values

    It's not the prettiest solution but without fixing mongoengine's email validation, this will have to do

    If you setup the following resource form:

    class SomeDocument(Document):
         email = EmailField(required=False)
         name = StringField()
    
    ResourceFormBase = model_form(SomeDocument, exclude=['email'])
    class ResourceForm(ResourceFormBase):
        email = fields.TextField(validators=[validators.optional(), validators.email()])
    

    A POST with the following json: {'name':'John Doe'} fails without these changes.

    Because form.data returns: {'name':'John Doe', 'email':''} instead of {'name':'John Doe'} which causes a validation error. There might be other consequences of this behavior.

    opened by lucasvo 4
  • Support for Python 3 / latest MongoEngine

    Support for Python 3 / latest MongoEngine

    This is tested on:

    • Python 2.7 and our own MongoEngine fork
    • Python 3.5 and upstream MongoEngine

    Note that I skipped two tests since upstream MongoEngine doesn't have certain features like SafeReferenceField.

    opened by thomasst 3
  • Don't return parent's child_document_resources in subclasses.

    Don't return parent's child_document_resources in subclasses.

    By default, don't inherit child_document_resources. This lets us have multiple resources for a child document without having to reset the child_document_resources property in the subclass.

    Consider the following example:

    class ParentResource(Resource):
        child_document_resources = { Child: 'ChildResource' }
    
    class ChildResource(ParentResource):
        document = Child
        fields = ['id']
    
    class VerboseChildResource(ChildResource):
        fields = ['id', 'foo', 'bar']
    

    If we call VerboseChildResource().serialize(obj), before this PR it would delegate the call to ChildResource since VerboseChildResource would inherit child_document_resources from ParentResource. This is usually not expected behavior, so I'm changing get_child_document_resources to only return the current class' child_document_resources. In the rare case where this isn't desirable, a parent can always explicitly change the behavior by overriding get_child_document_resources.

    opened by thomasst 3
  • Register class, BulkCreate/Delete, Flasgger, and other improvements

    Register class, BulkCreate/Delete, Flasgger, and other improvements

    This PR is a fresh start of #124. It includes:

    • A separate register_class function to use with Flask Blueprints (see e.g. here). This should supersede #85 and #115.
    • The register_class function also defines endpoints for each URL to enable integration with Flasgger (see this commit).
    • Implementation of BulkCreate and BulkDelete methods.
    • A typ property for operators to enable easier integration with Swagger definitions (see here).
    • Automatically trying to convert values to floats for numeric operators.
    • Static get_optional_fields method to allow retrieving optional fields from the class (see here)
    • Ensuring that order is maintained in get_requested_fields().
    • Forwarding the view_method when serializing.
    • Support for Decimal128 in MongoEncoder.
    • Improved error handling and kwargs forwarding.
    • Bugfix for #129.
    • mimerender dependency updated to include martinblech/mimerender#36

    @wojcikstefan @thomasst I'm working on getting the tests to pass and implement new ones for BulkCreate/Delete. Would you mind starting the review of this PR in the meantime? Thanks! I will base new PRs for additional functionality currently included in #124 off this PR.

    opened by tschaume 1
  • has_add_permission() cannot prevent object creation

    has_add_permission() cannot prevent object creation

    On views.py:162, object is created without save=True, so it's saved in database before has_add_permission is called, 5 lines below.

    I tried to create a PR with a fix, by first calling create_object(save=False), then self._resource.save_object(obj). But on tests/init.py:304 there's an explicit expectation that the unauthorized object have been saved.

    Is this really the expected behavior?

    opened by lfagundes 1
  • Saving Reference field along with object in POST request

    Saving Reference field along with object in POST request

    I would like to save the reference field along with object in post request. Is there any way to do this?

    For eg.

    class User(db.Document):
        name = db.StringField(required=True, unique=True)
    
    class Post(db.Document):
        # Some fields
        # ...
       author = db.ReferenceField(User)
    

    Now, I want to create author while making a POST request: /post/ => { "author": { "name": "Test" }, ..other fields }

    opened by anujism 0
  • Package requirements are broken in PyPI

    Package requirements are broken in PyPI

    Package requirements aren't recognized when installing from PyPI. Also, it could be helpful to move nose to something like

        extras_require={
            'test': ['nose'],
        },
    

    After that, it will be possible to setup test environment like that

    python setup.py develop
    pip install Flask-MongoRest[test]
    
    opened by lig 0
Releases(v0.3.0)
  • v0.3.0(Aug 26, 2019)

    The release:

    • Improves support for Python 3.
    • Bumps the dependency on pymongo.
    • Drops the dependency on Flask-Views. As a result, self.args and self.kwargs are no longer available in views.
    Source code(tar.gz)
    Source code(zip)
  • v0.2.3(Sep 27, 2017)

    This release splits Resource.serialize into separate field-type serialization methods, making it easier to add custom serialization logic for new field types.

    Source code(tar.gz)
    Source code(zip)
Owner
Close
The inside sales CRM of choice for SMBs. Join our eng team: http://jobs.close.com/
Close
A minimalistic manga reader for desktop built with React and Django

smanga A minimalistic manga reader/server for serving local manga images on desktop browser. Provides a two-page view layout just as reading a physica

Padam Upreti 13 Sep 24, 2022
Document Web APIs made with Django Rest Framework

DRF Docs Document Web APIs made with Django Rest Framework. View Demo Contributors Wanted: Do you like this project? Using it? Let's make it better! S

Manos Konstantinidis 626 Nov 20, 2022
Little Library API REST

Little Library API REST py 3.10 The only one requeriment it's to have Flask installed.

Luis Quiñones Requelme 1 Dec 15, 2021
DSpace REST API Client Library

DSpace Python REST Client Library This client library allows Python 3 scripts (Python 2 probably compatible but not officially supported) to interact

The Library Code GmbH 10 Nov 21, 2022
Swagger Documentation Generator for Django REST Framework: deprecated

Django REST Swagger: deprecated (2019-06-04) This project is no longer being maintained. Please consider drf-yasg as an alternative/successor. I haven

Marc Gibbons 2.6k Dec 23, 2022
A JSON Web Token authentication plugin for the Django REST Framework.

Simple JWT Abstract Simple JWT is a JSON Web Token authentication plugin for the Django REST Framework. For full documentation, visit django-rest-fram

Jazzband 3.3k Jan 04, 2023
Example Starlette REST API application

The idea of this project is to show how Starlette, Marshmallow, and SQLAlchemy can be combined to create a RESTful HTTP API application that is modular, lightweight, and capable of dealing with many

Robert Wikman 0 Jan 07, 2022
Allows simplified Python interaction with Rapid7's InsightIDR REST API.

InsightIDR4Py Allows simplified Python interaction with Rapid7's InsightIDR REST API. InsightIDR4Py allows analysts to query log data from Rapid7 Insi

Micah Babinski 8 Sep 12, 2022
Django-rest-auth provides a set of REST API endpoints for Authentication and Registration

This app makes it extremely easy to build Django powered SPA's (Single Page App) or Mobile apps exposing all registration and authentication related functionality as CBV's (Class Base View) and REST

Tivix 2.4k Dec 29, 2022
Django queries

Djaq Djaq - pronounced “Jack” - provides an instant remote API to your Django models data with a powerful query language. No server-side code beyond t

Paul Wolf 53 Dec 12, 2022
REST implementation of Django authentication system.

djoser REST implementation of Django authentication system. djoser library provides a set of Django Rest Framework views to handle basic actions such

Sunscrapers 2.2k Jan 01, 2023
A RESTful whois

whois-rest A RESTful whois. Installation $ pip install poetry $ poetry install $ uvicorn app:app INFO: Started server process [64616] INFO: W

Manabu Niseki 4 Feb 19, 2022
Kong API Manager with Prometheus And Splunk

API Manager Stack Run Kong Server + Konga + Prometheus + Grafana + API & DDBB + Splunk Clone the proyect and run docker-compose up

Santiago Fernandez 82 Nov 26, 2022
A Django api to display items and their current up-to-date prices from different online retailers in one platform.

A Django api to display items and their current up-to-date prices from different online retailers in one platform. Utilizing scrapy to periodically scrape the latest prices from different online reta

Kennedy Ngugi Mwaura 1 Nov 05, 2021
Recursive Serialization for Django REST framework

djangorestframework-recursive Overview Recursive Serialization for Django REST framework This package provides a RecursiveField that enables you to se

336 Dec 28, 2022
Django Ninja is a web framework for building APIs with Django and Python 3.6+ type hints.

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs

Vitaliy Kucheryaviy 3.8k Jan 04, 2023
BloodDonors: Built using Django REST Framework for the API backend and React for the frontend

BloodDonors By Daniel Yuan, Alex Tian, Aaron Pan, Jennifer Yuan As the pandemic raged, one of the side effects was an urgent shortage of blood donatio

Daniel Yuan 1 Oct 24, 2021
Flask RestAPI Project - Transimage Rest API For Python

[ 이미지 변환 플라스크 Rest API ver01 ] 0. Flask Rest API - in SunnyWeb : 이미지 변환 웹의 Flask

OliverKim 1 Jan 12, 2022
Eazytraining - Simple application to show how to query API from webapp

student-list Eazytraining - Simple application to show how to query API from webapp This repo is a simple application to list student with a webserver

⚡Christophe FREIJANES 2 Nov 15, 2021
A Django-powered API with various utility apps / endpoints.

A Django-powered API Includes various utility apps / endpoints. Demos These web apps provide a frontend to the APIs in this project. Issue API Explore

Shemar Lindie 0 Sep 13, 2021