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
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.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 library that makes consuming a RESTful API easier and more convenient

Slumber is a Python library that provides a convenient yet powerful object-oriented interface to ReSTful APIs. It acts as a wrapper around the excellent requests library and abstracts away the handli

Sam Giles 597 Dec 13, 2022
An easy-to-use high-performance asynchronous web framework.

An easy-to-use high-performance asynchronous web framework.

Aber 264 Dec 31, 2022
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
Containers And REST APIs Workshop

Containers & REST APIs Workshop Containers vs Virtual Machines Ferramentas Podman: https://podman.io/ Docker: https://www.docker.com/ IBM CLI: https:/

Vanderlei Munhoz 8 Dec 16, 2021
FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins.

FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins. It is based on top of fastAPI, uvicorn, typer, and pluggy.

Adrien Delsalle 1 Nov 16, 2021
cirrina is an opinionated asynchronous web framework based on aiohttp

cirrina cirrina is an opinionated asynchronous web framework based on aiohttp. Features: HTTP Server Websocket Server JSON RPC Server Shared sessions

André Roth 32 Mar 05, 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
Endpoints is a lightweight REST api framework written in python and used in multiple production systems that handle millions of requests daily.

Endpoints Quickest API builder in the West! Endpoints is a lightweight REST api framework written in python and used in multiple production systems th

Jay Marcyes 30 Mar 05, 2022
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
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
Web3.py plugin for using Flashbots' bundle APIs

This library works by injecting a new module in the Web3.py instance, which allows submitting "bundles" of transactions directly to miners. This is do

Flashbots 293 Dec 31, 2022
Appier is an object-oriented Python web framework built for super fast app development.

Joyful Python Web App development Appier is an object-oriented Python web framework built for super fast app development. It's as lightweight as possi

Hive Solutions 122 Dec 22, 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
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
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 Jan 08, 2023
🔥 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
Daniel Vaz Gaspar 4k Jan 08, 2023
A public API written in Python using the Flask web framework to determine the direction of a road sign using AI

python-public-API This repository is a public API for solving the problem of the final of the AIIJC competition. The task is to create an AI for the c

Lev 1 Nov 08, 2021