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
Ape is a framework for Web3 Python applications and smart contracts, with advanced functionality for testing, deployment, and on-chain interactions.

Ape Framework Ape is a framework for Web3 Python applications and smart contracts, with advanced functionality for testing, deployment, and on-chain i

ApeWorX Ltd. 552 Dec 30, 2022
Asita is a web application framework for python based on express-js framework.

Asita is a web application framework for python. It is designed to be easy to use and be more easy for javascript users to use python frameworks because it is based on express-js framework.

Mattéo 4 Nov 16, 2021
Fast⚡, simple and light💡weight ASGI micro🔬 web🌏-framework for Python🐍.

NanoASGI Asynchronous Python Web Framework NanoASGI is a fast ⚡ , simple and light 💡 weight ASGI micro 🔬 web 🌏 -framework for Python 🐍 . It is dis

Kavindu Santhusa 8 Jun 16, 2022
web.py is a web framework for python that is as simple as it is powerful.

web.py is a web framework for Python that is as simple as it is powerful. Visit http://webpy.org/ for more information. The latest stable release 0.62

5.8k Dec 30, 2022
A shopping list and kitchen inventory management app.

Flask React Project This is the backend for the Flask React project. Getting started Clone this repository (only this branch) git clone https://github

11 Jun 03, 2022
Screaming-fast Python 3.5+ HTTP toolkit integrated with pipelining HTTP server based on uvloop and picohttpparser.

Japronto! There is no new project development happening at the moment, but it's not abandoned either. Pull requests and new maintainers are welcome. I

Paweł Piotr Przeradowski 8.6k Dec 29, 2022
A proof-of-concept CherryPy inspired Python micro framework

Varmkorv Varmkorv is a CherryPy inspired micro framework using Werkzeug. This is just a proof of concept. You are free to use it if you like, or find

Magnus Karlsson 1 Nov 22, 2021
The Python micro framework for building web applications.

Flask Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to co

The Pallets Projects 61.5k Jan 06, 2023
A microservice written in Python detecting nudity in images/videos

py-nudec py-nudec (python nude detector) is a microservice, which scans all the images and videos from the multipart/form-data request payload and sen

Michael Grigoryan 8 Jul 09, 2022
The Modern And Developer Centric Python Web Framework. Be sure to read the documentation and join the Slack channel questions: http://slack.masoniteproject.com

NOTE: Masonite 2.3 is no longer compatible with the masonite-cli tool. Please uninstall that by running pip uninstall masonite-cli. If you do not unin

Masonite 1.9k Jan 04, 2023
Try to create a python mircoservice framework.

Micro current_status: prototype. ... Python microservice framework. More in Document. You should clone this project and run inv docs. Install Not now.

修昊 1 Dec 07, 2021
The web framework for inventors

Emmett is a full-stack Python web framework designed with simplicity in mind. The aim of Emmett is to be clearly understandable, easy to be learned an

Emmett 796 Dec 26, 2022
Embrace the APIs of the future. Hug aims to make developing APIs as simple as possible, but no simpler.

Read Latest Documentation - Browse GitHub Code Repository hug aims to make developing Python driven APIs as simple as possible, but no simpler. As a r

Hug API Framework 6.7k Dec 27, 2022
Bablyon 🐍 A small ASGI web framework

A small ASGI web framework that you can make asynchronous web applications using uvicorn with using few lines of code

xArty 8 Dec 07, 2021
News search API developed for the purposes of the ColdCase Project.

Saxion - Cold Case - News Search API Setup Local – Linux/MacOS Make sure you have python 3.9 and pip 21 installed. This project uses a MySQL database,

Dimitar Rangelov 3 Jul 01, 2021
The source code to the Midnight project

MidnightSniper Started: 24/08/2021 Ended: 24/10/2021 What? This is the source code to a project developed to snipe minecraft names Why release? The ad

Kami 2 Dec 03, 2021
A simple Tornado based framework designed to accelerate web service development

Toto Toto is a small framework intended to accelerate web service development. It is built on top of Tornado and can currently use MySQL, MongoDB, Pos

Jeremy Olmsted-Thompson 61 Apr 06, 2022
CherryPy is a pythonic, object-oriented HTTP framework. https://docs.cherrypy.org/

Welcome to the GitHub repository of CherryPy! CherryPy is a pythonic, object-oriented HTTP framework. It allows building web applications in much the

CherryPy 1.6k Dec 29, 2022
Low code web framework for real world applications, in Python and Javascript

Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library.

Frappe 4.3k Dec 30, 2022
Web-frameworks-benchmark

Web-frameworks-benchmark

Nickolay Samedov 4 May 13, 2021