closeio / flask-mongorest

Restful API framework wrapped around MongoEngine
Other
523 stars 88 forks source link

How to use Flask-MongoRest resource filters? #105

Closed KeironO closed 7 years ago

KeironO commented 7 years ago

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?

KeironO commented 7 years ago

Issues with the Document schema are now fixed, new error being returned.

from mongoengine import *

class Positive(EmbeddedDocument):
    count = IntField()
    peaks = StringField() # Temporary

class Negative(EmbeddedDocument):
    count = IntField()
    peaks = StringField() # Temporary

class AdductWeights(EmbeddedDocument):
    negative = EmbeddedDocumentField(Negative)
    positive = EmbeddedDocumentField(Positive)
    neutral = FloatField()

class MetaboliteAdduct(Document):
    meta = { "collection" : "metabolites"}
    name = StringField()
    adduct_weights = EmbeddedDocumentField(AdductWeights)

Now returns AttributeError: type object 'AdductWeights' has no attribute 'objects'.

Traceback (most recent call last):
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1994, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "/home/keo7/.local/lib/python2.7/site-packages/mimerender.py", line 244, in wrapper
    result = target(*args, **kwargs)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask_mongorest/views.py", line 56, in dispatch_request
    return self._dispatch_request(*args, **kwargs)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask_mongorest/views.py", line 68, in _dispatch_request
    return super(ResourceView, self).dispatch_request(*args, **kwargs)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask_views/base.py", line 36, in dispatch_request
    return super(View, self).dispatch_request(*args, **kwargs)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask/views.py", line 149, in dispatch_request
    return meth(*args, **kwargs)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask_mongorest/views.py", line 107, in get
    result = self._resource.get_objects(qfilter=qfilter)
  File "/home/keo7/.local/lib/python2.7/site-packages/flask_mongorest/resources.py", line 756, in get_objects
    qs = self.get_queryset()
  File "/home/keo7/.local/lib/python2.7/site-packages/flask_mongorest/resources.py", line 546, in get_queryset
    return self.document.objects
AttributeError: type object 'AdductWeights' has no attribute 'objects'
wojcikstefan commented 7 years ago

Are you using AdductWeights as a document in your resource? EmbeddedDocuments can't be used this way, only Document classes. EmbeddedDoc.objects indeed doesn't exist.

KeironO commented 7 years ago

These are my resources.

class PositiveResource(Resource):
    document = d.Positive

class NegativeResource(Resource):
    document = d.Negative

class AdductWeightsResource(Resource):
    document = d.AdductWeights
    related_resources = {
        "positive" : PositiveResource,
        "negative" : NegativeResource
    }

class MetaboliteAdductResource(Resource):
    document = d.MetaboliteAdduct
    related_resources = {
        "adduct_weight" : AdductWeightsResource
    }

How else am I supposed to be using them?

wojcikstefan commented 7 years ago

First of all, thanks @KeironO for uncovering all the things that should be covered by the documentation. Appreciate your inquiries :)

Change "adduct_weight" to "adduct_weights" in related_resources.

Edit: Removed previous invalid answer.

Note to self: typo in the field name in related_resources should raise a more obvious error.

KeironO commented 7 years ago

@wojcikstefan not a problem, once I get my head around it I'll probably go about creating a simple-CMS to demonstrate said functionality simplistically for others.

Even when I change the "related_resources" field name, the same error is given.

Documents:

class Positive(EmbeddedDocument):
    count = IntField()
    peaks = StringField() # Temporary

class Negative(EmbeddedDocument):
    count = IntField()
    peaks = StringField() # Temporary

class AdductWeights(EmbeddedDocument):
    negative = EmbeddedDocumentField(Negative)
    positive = EmbeddedDocumentField(Positive)
    neutral = FloatField()

class MetaboliteAdduct(Document):
    meta = {"collection" : "metabolites"}
    name = StringField()
    adduct_weights = EmbeddedDocumentField(AdductWeights)

Resources

class PositiveResource(Resource):
    document = d.Positive

class NegativeResource(Resource):
    document = d.Negative

class AdductWeightsResource(Resource):
    document = d.AdductWeights
    related_resources = {
        "positive" : PositiveResource,
        "negative" : NegativeResource
    }

class MetaboliteAdductResource(Resource):
    document = d.MetaboliteAdduct
    related_resources = {
        "adduct_weights" : AdductWeightsResource
    }
KeironO commented 7 years ago

Update: I managed to fix that issue, I'm just struggling to get the operators to work.

Documents:


class PositiveAdduct(EmbeddedDocument):
    count = IntField()
    peaks = StringField()

class NegativeAdduct(EmbeddedDocument):
    count = IntField()
    peaks = StringField()

class AdductWeights(EmbeddedDocument):
    neutral = FloatField()
    positive = EmbeddedDocumentField(PositiveAdduct)
    negative = EmbeddedDocumentField(NegativeAdduct)

class MetaboliteAdduct(DynamicDocument):
    meta = {"collection" : "metabolites"}
    name = StringField()
    adduct_weights = EmbeddedDocumentField(AdductWeights)

Resources:

class NegativeAdductResource(Resource):
    document = d.NegativeAdduct
    filters = {
        "count" : [ops.Gt]
    }

class PositiveAdductResource(Resource):
    document = d.PositiveAdduct

class AdductWeightsResource(Resource):
    document = d.AdductWeights

    related_resources = {
        "positive" : PositiveAdductResource,
        "negative" : NegativeAdductResource
    }

class MetaboliteAdductResource(Resource):
    document = d.MetaboliteAdduct
    filters = {
        "name" : [ops.Exact]
    }

    related_resources = {
        "adduct_weights" : AdductWeightsResource
    }

When I attempt to query the API using the following request:

adducts/?adduct_weights_negative_count__gt=0

Nothing seems to happen!

wojcikstefan commented 7 years ago

@KeironO remove the filters from the sub-resources and on the main MetaboliteAdductResource add:

    filters = {
        'adduct_weights__positive__count': [ops.Gt],
        'adduct_weights__negative__count': [ops.Gt]
    }

Then, you can query it by doing e.g. GET /metabolite/?adduct_weights__negative__count__gt=2. Note that subfields are separated from fields the same way as operators - by double underscores.

KeironO commented 7 years ago

I owe you a beer!

wojcikstefan commented 7 years ago

Haha, no problem mate. Thanks for pointing out some parts where Flask-MongoRest is not intuitive nor documented well. It's very helpful to see the problems through a new adopter's eyes!

I think we can close this issue now. I renamed it so that it's easier to find by others.