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
Python AsyncIO data API to manage billions of resources

Introduction Please read the detailed docs This is the working project of the next generation Guillotina server based on asyncio. Dependencies Python

Plone Foundation 183 Nov 15, 2022
Library for building WebSocket servers and clients in Python

What is websockets? websockets is a library for building WebSocket servers and clients in Python with a focus on correctness and simplicity. Built on

Aymeric Augustin 4.3k Dec 31, 2022
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
aiohttp-ratelimiter is a rate limiter for the aiohttp.web framework.

aiohttp-ratelimiter aiohttp-ratelimiter is a rate limiter for the aiohttp.web fr

JGL Technologies 4 Dec 11, 2022
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
The core of a service layer that integrates with the Pyramid Web Framework.

pyramid_services The core of a service layer that integrates with the Pyramid Web Framework. pyramid_services defines a pattern and helper methods for

Michael Merickel 78 Apr 15, 2022
🔥 Fire up your API with this flamethrower

🔥 Fire up your API. Documentation: https://flama.perdy.io Flama Flama aims to bring a layer on top of Starlette to provide an easy to learn and fast

José Antonio Perdiguero 216 Dec 26, 2022
Async Python 3.6+ web server/framework | Build fast. Run fast.

Sanic | Build fast. Run fast. Build Docs Package Support Stats Sanic is a Python 3.6+ web server and web framework that's written to go fast. It allow

Sanic Community Organization 16.7k Dec 28, 2022
Flask like web framework for AWS Lambda

lambdarest Python routing mini-framework for AWS Lambda with optional JSON-schema validation. ⚠️ A user study is currently happening here, and your op

sloev / Johannes Valbjørn 91 Nov 10, 2022
A PC remote controller for YouTube and Twitch

Lazynite Lazynite is a PC remote controller for YouTube and Twitch on Telegram. Features Volume control; Browser fullscreen / video fullscreen; PC shu

Alessio Celentano 46 Nov 12, 2022
The Web framework for perfectionists with deadlines.

Django Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Thanks for checking it out. All docu

Django 67.9k Dec 29, 2022
Light, Flexible and Extensible ASGI API framework

Starlite Starlite is a light, opinionated and flexible ASGI API framework built on top of pydantic and Starlette. Check out the Starlite documentation

Na'aman Hirschfeld 1.6k Jan 09, 2023
Full duplex RESTful API for your asyncio web apps

TBone TBone makes it easy to develop full-duplex RESTful APIs on top of your asyncio web application or webservice. It uses a nonblocking asynchronous

TBone Framework 37 Aug 07, 2022
An alternative serializer implementation for REST framework written in cython built for speed.

drf-turbo An alternative serializer implementation for REST framework written in cython built for speed. Free software: MIT license Documentation: htt

Mng 74 Dec 30, 2022
A tool for quickly creating REST/HATEOAS/Hypermedia APIs in python

ripozo Ripozo is a tool for building RESTful/HATEOAS/Hypermedia apis. It provides strong, simple, and fully qualified linking between resources, the a

Vertical Knowledge 198 Jan 07, 2023
PipeLayer is a lightweight Python pipeline framework

PipeLayer is a lightweight Python pipeline framework. Define a series of steps, and chain them together to create modular applications

greaterthan 64 Jul 21, 2022
Asynchronous HTTP client/server framework for asyncio and Python

Async http client/server framework Key Features Supports both client and server side of HTTP protocol. Supports both client and server Web-Sockets out

aio-libs 13.2k Jan 05, 2023
Bionic is Python Framework for crafting beautiful, fast user experiences for web and is free and open source

Bionic is fast. It's powered core python without any extra dependencies. Bionic offers stateful hot reload, allowing you to make changes to your code and see the results instantly without restarting

⚓ 0 Mar 05, 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
You can use the mvc pattern in your flask application using this extension.

You can use the mvc pattern in your flask application using this extension. Installation Run the follow command to install mvc_flask: $ pip install mv

Marcus Pereira 37 Dec 17, 2022