Coder Social home page Coder Social logo

graphql-python / graphene-mongo Goto Github PK

View Code? Open in Web Editor NEW
285.0 14.0 114.0 642 KB

Graphene MongoEngine integration

Home Page: http://graphene-mongo.readthedocs.io/en/latest/

License: MIT License

Python 99.66% Makefile 0.34%
graphene mongoengine graphql graphene-mongo

graphene-mongo's Introduction

Build Status Coverage Status Documentation Status PyPI version PyPI pyversions Downloads

Lint Test Package

Graphene-Mongo

A Mongoengine integration for Graphene.

Installation

For installing graphene-mongo, just run this command in your shell

pip install graphene-mongo

Examples

Here is a simple Mongoengine model as models.py:

from mongoengine import Document
from mongoengine.fields import StringField


class User(Document):
    meta = {'collection': 'user'}
    first_name = StringField(required=True)
    last_name = StringField(required=True)

To create a GraphQL schema and sync executor; for it you simply have to write the following:

import graphene

from graphene_mongo import MongoengineObjectType

from .models import User as UserModel


class User(MongoengineObjectType):
    class Meta:
        model = UserModel


class Query(graphene.ObjectType):
    users = graphene.List(User)

    def resolve_users(self, info):
        return list(UserModel.objects.all())


schema = graphene.Schema(query=Query)

Then you can simply query the schema:

query = '''
    query {
        users {
            firstName,
            lastName
        }
    }
'''
result = await schema.execute(query)

To create a GraphQL schema and async executor; for it you simply have to write the following:

import graphene

from graphene_mongo import AsyncMongoengineObjectType
from graphene_mongo.utils import sync_to_async
from concurrent.futures import ThreadPoolExecutor

from .models import User as UserModel


class User(AsyncMongoengineObjectType):
    class Meta:
        model = UserModel


class Query(graphene.ObjectType):
    users = graphene.List(User)

    async def resolve_users(self, info):
        return await sync_to_async(list, thread_sensitive=False,
                             executor=ThreadPoolExecutor())(UserModel.objects.all())


schema = graphene.Schema(query=Query)

Then you can simply query the schema:

query = '''
    query {
        users {
            firstName,
            lastName
        }
    }
'''
result = await schema.execute_async(query)

To learn more check out the following examples:

Contributing

After cloning this repo, ensure dependencies are installed by running:

pip install -r requirements.txt

After developing, the full test suite can be evaluated by running:

make test

graphene-mongo's People

Contributors

abawchen avatar abhinand-c avatar adithyanjothir avatar alexpantyukhin avatar arun-sureshkumar avatar arunsureshkumar avatar barseghyanartur avatar benoittoulet avatar blochsbek avatar claradaia avatar crunk1 avatar dependabot[bot] avatar donqueso89 avatar eduarde avatar fabianriewe avatar floatingghost avatar gordol avatar leonardwellthy avatar mak626 avatar n3hrox avatar riverfr0zen avatar rohitchattopadhyay avatar schattian avatar sostholm avatar tomreichardt avatar wax911 avatar yun17 avatar zrlay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphene-mongo's Issues

Slicing for last n nodes is broken

Using the slicing pagination for getting the first n items works, but asking for the last n doesn't. Doing this query for example works:

{
  questions(first: 2) {
    edges {
      node{
        id
      }
      cursor
    }
  }
}

You get a result like this

{
  "data": {
    "questions": {
      "edges": [
        {
          "node": {
            "id": "UXVlc3Rpb246NWFiNmM1OTY0YmFhM2MwMDA0YzIzMjBl"
          },
          "cursor": "YXJyYXljb25uZWN0aW9uOjA="
        },
        {
          "node": {
            "id": "UXVlc3Rpb246NWFiNmM1OTg0YmFhM2MwMDA0YzIzMjBm"
          },
          "cursor": "YXJyYXljb25uZWN0aW9uOjE="
        }
      ]
    }
  }
}

But doing this query breaks:

{
  questions(last: 2) {
    edges {
      node {
        id
      }
      cursor
    }
  }
}

and give you something like this

{
  "errors": [
    {
      "message": "stop index must be greater than startindex for slice slice(None, -2, None)",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ],
  "data": {
    "questions": null
  }
}

[BUG] Document subclasses are cast to parent class in query results

I know I'm missing something. Given a model with multiple document types in a collection:

class AModel(Document):
    meta = {'allow_inheritance': True}
    a_field = StringField()

class ASubModel(AModel):
    a_sub_field = StringField()

I can construct a schema and query:

class A(MongoengineObjectType):                                           
    class Meta:                                                                
        model = AModel                                                    
        interfaces = (Node,)                                                   

class ASub(A):                                                      
    class Meta:                                                                
        model = ASubModel                                                
        interfaces = (Node,)

class Query(graphene.ObjectType):                                              
    node = Node.Field()                                                        
    all_a = MongoengineConnectionField(A)                           

If I run the allA query, I get back all the documents as expected, but I can't access ASub's fields because the returned type is A, not ASub, even though the class is identified as A.ASub. E.g.,:

{
  "data": {
    "allA": {
      "edges": [
        {
          "node": {
            "id": "VGFyZ2V0OjVjZTVmOTMwMjRmN2NhMGJmMjZlNzZmMQ==",
            "Cls": "A.ASub",
            "__typename": "A"
          }
        }
     ]
  }
}

Attempting to resolve ASub.a_sub_field results in an error.
Inline fragment doesn't work either:

{
  "errors": [
    {
      "message": "Fragment cannot be spread here as objects of type A can never be of type ASub",
      "locations": [
        {
          "line": 8,
          "column": 9
        }
      ]
    }
  ]
}

I'm clearly doing something wrong, but I don't know what.

AttributeError: 'NoneType' object has no attribute '_type'

I am receiving an error node = kv[1].get_type()._type._meta
AttributeError: 'NoneType' object has no attribute '_type'

when I am using "EmbeddedDocument(LatLang)"

class LatLng(EmbeddedDocument):
lat = FloatField()
lng = FloatField()

class User(Document):
email = StringField(required=True)
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
location = EmbeddedDocumentField(LatLng)

Support for default mutations

Are there any plans to add support for simple create / update / delete mutations? It seems like you could view the fields associated with a Mongoengine Document and automatically build the arguments to a graphene mutation, instead of needing to manually write one for each object. Thoughts on this?

Cannot return null for non-nullable field Type.field.

If I create add a new field and the field is null, it leads to an error in
Cannot return null for non-nullable field Type.field.

In my model, I didn't set required to true for that field. After inspecting I found this in converter.py
String(description=field.db_field, required=not field.null)
which sets all field to required.

Add support before and after on Pagination and Edges.

Support for filtering edges on based on cursors. Currently the libarary returns an error when you ask it something like this:

{
  questions(first: 2, after: "YXJyYXljb25uZWN0aW9uOjE=") {
    edges {
      node {
        id
      }
      cursor
    }
  }
}

It will return this kind of error

{
  "errors": [
    {
      "message": "Cannot resolve field \"after\"",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ],
  "data": {
    "questions": null
  }
}

Allow subclassing MongoengineObjectType

Right now, extending MongoengineObjectType by subclassing is not possible in any useful way as __init_subclass_with_meta__ is immediately called on encountering a descendant class definition. This makes several assumptions, e.g. Meta.model being defined on the subclass. Extension classes might want to deduce the model via their subclasses and generally wouldn't know about it at the point of class body execution.

The proposed solution is some mechanism to defer initialization until manually requested, if some flag (e.g. manual_init) is passed in Meta.

My current workaround is using multiple inheritance which allows customization. A superficial example - let's say we wish to use quux instead of model for the Meta key defining the model:

class Enhancer:
    @classmethod
    def __init_subclass_with_meta__(cls, quux, **kwargs):
        kwargs["model"] = quux
        super().__init_subclass_with_meta__(**kwargs)        

class AssetSchema(Enhancer, MongoengineObjectType):
    class Meta:
        interfaces = (relay.Node,)
        quux = Asset

Support for MultipolygonField

Hey guys, just came accross the need for a PolygonField representation.

I tried hacking my way into it by looking at #58 and it seemed very straightforward.

Could give it a shot and send PR if that's OK

Is there a way to filter results?

Do you have a way to filter queries? I understand that the graphene-django project relied on django-filter for query filtering. Is there something similar already implemented in this project?

I am trying to perform the following query:

query {
    allUsers(name: "foo"){
        edges{
            node{
                id,
                name
            }
        }
    }
}

Bidirectional relationships require a specific load order

Using the following script as a test case

If Parent is registered before Child, when extracting types from Parent.Meta.model no type corresponding to ReferenceField("ChildModel") will be found in the registry, leading to the children attribute to be unqueryable.

If Child is registered before Parent, however, this issue does not arise.

The point of lazy field loading is to avoid these load-order shenanigans - I think if type conversion can be delayed that might work.

I'll poke around on my end, see if I can't find out how to get this thing to work.

from graphene_mongo import MongoengineConnectionField
from mongoengine import connect, Document
from mongoengine.fields import (StringField, ListField, ReferenceField)
from graphene_mongo import MongoengineObjectType
from graphene.relay import Node
import graphene
import json
    
connect("TestCollection")


class ParentModel(Document):
    meta = {"collection": "Parents"}

    name = StringField()
    children = ListField(ReferenceField("ChildModel"))


class ChildModel(Document):
    meta = {"collection": "Children"}

    name = StringField()
    parent = ReferenceField("ParentModel")


ChildModel.objects.all().delete()
ParentModel.objects.all().delete()

# Create a child
child = ChildModel(name="My parent is parent_a!")
child.save()

# Create two parents
parent_a = ParentModel(name="ThisParentWillHaveAChild", children=[child])
parent_b = ParentModel(name="ThisParentHasNoChildren", children=[])
parent_a.save()
parent_b.save()
        
child.parent = parent_a
child.save()


class Parent(MongoengineObjectType):
    class Meta: 
        model = ParentModel 
        interfaces = (Node, )


class Child(MongoengineObjectType):
    class Meta:
        model = ChildModel
        interfaces = (Node, )

class Query(graphene.ObjectType):
    node = Node.Field()

    parents = MongoengineConnectionField(Parent)
    children = graphene.List(Child)


schema = graphene.Schema(query=Query, types=[Parent, Child])

x = schema.execute("""
query {
    parents {
        edges {
            node {
                name,
                children {
                    edges {
                        node {
                            name,
                            parent { name }
                        }
                    }
                }
            }
        }
    }
}
""")

print("ERR:", x.errors)
print("DATA: {}".format(json.dumps(x.data)))

Support for cursor based pagination

Hi,

how can I use cursor based pagination (page=page, per_page=...) with your library ? I can't modify the mongo request using something like this:

def resolve_users(self, info):
    	return list(UserModel.objects.find({...}).sort(...).limit(limit))

because then I get 'QuerySet' object has no attribute 'find'

Please provide information regarding this functionality or implement this feature.

Polygon Field support

Is there a support for the PolygonField? I only could find the MultiPolygon.

Thanks so much :)

Problems When Using EmbeddedDocumentListField

Python Version: Python 3.7

Error: TypeError: '<' not supported between instances of 'ID' and 'ConfigurationSpec'

Essentially, I have an embedded document modeled as such:

class ConfigurationSpec(EmeddedDocument):
     first_field = StringField(db_field="First Field")
     second_field = StringField(db_field="Second Field")
     ...

And then, I pull in the embedded document into a larger model. This larger model is built of embedded documents. The standalone embedded documents seem to work fine. When I use the field embedded document list field it gives me a couple of problems.

The larger document is modeled as:

class LargeSpecModel(Document):
     meta = {'collection': 'specifications'}
     list_of_configs = EmbeddedDocumentListField(ConfigurationSpec)

This breaks ๐Ÿ˜Ÿ.

What does "default_query" in flask_mongoengine example do?

In the flask mongoengine example, in app.py, a 'default_query' is declared.

I had assumed that there was some magic in the background that would cause this default query to be populated by default when the graphiql view is requested from the browser.

However, this doesn't happen in my implementation attempt -- the query pane is just empty. The rest of the graphiql interface works just fine. Searching code in both graphene-mongo and flask-graphql doesn't seem to turn up any references to 'default_query'.

  1. Is my expectation above correct, i.e. is this a way to populate the graphiql interface with a default query?

  2. If the expectation is correct, then maybe it's not working for me because I'm declaring default_query within a flask app factory method? How would I set the default query in this case?

InputObjectType fields from models

I've used a MongoengineObjectType class to define the schema from model.
There's a way to use a reference model to define a InputObjectType or AbstractType class? Something like that:

class UserFields(graphene.AbstractType):
	class Meta:
		model = model.User

class User(graphene.ObjectType, UserFields):
	pass

class UserInput(graphene.InputObjectType, UserFields):
	pass


GenericReferenceField support

Hi, currently the field converter throws an exception on MongoEngine's GenericReferenceField:

Exception: Don't know how to convert the MongoEngine field <mongoengine.fields.GenericReferenceField object at 0x7f24dc4d03c8> (<class 'mongoengine.fields.GenericReferenceField'>)

It would be great to have support for this field type.

edges & node attributes in result set

Hi,
I am wondering why there is an edges and nodes attributes in the result set? It seems to break the concept of being able to have a consistent object model, query language and result set.

Querying based on value of ReferenceField

I have a model defined like so:

from mongoengine import Document, StringField, ReferenceField

class Bar(Document):
    pass

class Foo(Document):
    parent = ReferenceField('Bar')
    name = StringField(default='')

If I set up my schema like this:

    from graphene_mongo import MongoengineConnectionField, MongoengineObjectType
    from .models import (Foo as FooModel, Bar as BarModel)

    class Bar(MongoengineObjectType):
        class Meta:
            model = BarModel
            interfaces = (Node,)

    class Foo(MongoengineObjectType):
        class Meta:
            model = FooModel
            interfaces = (Node,)


    class Query(graphene.ObjectType):
        node = Node.Field()
        foos = MongoengineConnectionField(Foo)
        bars = MongoengineConnectionField(Bar)

    schema = graphene.Schema(query=Query, types=[Foo, Bar])

Should I be able to query for Foo with parent equal to the id of a Bar? Example query:

{ 
  foos(parent: "some_valid_id_in_string_form") {
    edges {
      node {
        name
      }
    }
  }
}

Returning a single object in graphene.field

I am new to graphql and grahene
I am attempting to hydrate a MongoengineObjectType from my resolve method

I have a few pre-defined filter rules that need to run before I can return the data, but I cant seem to return the model itself

`class UserNode(MongoengineObjectType):
class Meta:
model = User

class Query(graphene.ObjectType):
user = graphene.Field(UserNode, id=graphene.String())

def resolve_user(self, info, id):
auth_user = info.context.get('user')
user = User.objects.get(userId=id, foo_Id=auth_user.foo_Id)
return user`

Time Query Last Register

Dear,

I have the next question.

The following query with specific delay filter 118 ms

Screen Shot 2019-05-20 at 16 21 45

And this with "last: 1" delay filter 9,1 s

Screen Shot 2019-05-20 at 16 21 01

It's the same data. (same records)

The time is related to Waiting (TTFB).
Can you tell me if I'm making a mistake?

Use of DataLoader to solve exorbitant query times

There's a dramatic reduction in query performance when using GraphQL with graphene-mongo, as opposed to a regular REST API doing something like list(collection.objects.all()) and sending the entire document set. From 300ms to over 6 seconds to retrieve one field from 100 elements.

I believe that using the DataLoader pattern is the expected solution to the N+1 problem, but actually attempting to use it will cause a runtime error

graphql.error.located_error.GraphQLLocatedError: Data loader batch_load_fn function raised an Exception: AttributeError("type object 'Person' has no attribute 'objects'",)

Why is BooleanField non-null?

I had a model:

class MyModel(EmbeddedDocument):
    myField = BooleanField(null=True)

and was getting an error about "Cannot return null for non-nullable". It seemed that it didn't matter if I set null=True, null=False. Digging into this, I found:

@convert_mongoengine_field.register(mongoengine.BooleanField)
def convert_field_to_boolean(field, registry=None):
return NonNull(Boolean, description=field.db_field)

Why is this?

Self referencing model

Hello and thx for your lib,

I'd like to self-reference a model, but I'm not sure how to do it as I'm quite new to Python, what I do is:

class Player(Document):
    created_on = DateTimeField(default=datetime.now, db_field='d')
    name = StringField(required=True, db_field='n')

Player.friends = ListField(ReferenceField(Player))

But I get an AttributeError: 'NoneType' object has no attribute '_type' in python3.6/site-packages/graphene_mongo/converter.py

Am I doing it wrong or is it not possible?

async (motorengine)

I'm trying to get a stack of Sanic, mongodb and graphql to work. All async..
I can do mongo async using https://github.com/heynemann/motorengine, but then I have no way of using graphene-mongo as it wants a valid Mongoengine Model.

I can't find any issues on this in this repo, so I might be getting this wrong, but is there plans for supporting async? Or anyway I can hack it in using the current graphene-mongo?

Adding Examples for Starlette

It would be nice to add some examples about integrating graphene_mongo with starlette to build awesome API libraries.

Filtering nested document. Resolvers?

I'd like to filter nested documents

models:

class Internal(EmbeddedDocument):
    potential = fields.IntField(min_value=1, max_value=3)

class Lead(Document):
    meta = {'collection': 'lead'}
    id = fields.ObjectIdField(required=True, primary_key=True, default=ObjectId)
    internal = fields.EmbeddedDocumentField(Internal)

query:

{
  leads {
    edges {
      node {
        id
        internal(potential: 1) {
          potential
        }
      }
    }
  }
}

but response is:

{
  "errors": [
    {
      "message": "Unknown argument \"filter\" on field \"internal\" of type \"LeadNode\".",
      "locations": [
        {
          "line": 6,
          "column": 18
        }
      ]
    }
  ]
}

I'm trying to achieve filtering by resolvers:

class Query(graphene.ObjectType):
    node = Node.Field()
    leads = MongoengineConnectionField(LeadNode)

    leads_by_internal = graphene.Field(
        LeadNode,
        internal__potential=graphene.Int(),
        internal__warmth=graphene.Int(),
        internal__next_step=graphene.Int(),
    )

    def resolve_leads_by_internal(_, __, **kwargs):
        qs = Lead.objects.filter(**kwargs)
        return qs

But I don't know, what type resolver should return

Any help guys, pls?

Not able to request EmbeddedDocumentField in query

Hi,

I have the following models:

class ProfessorMetadata(EmbeddedDocument):
    id = StringField()
    first_name = StringField()
    last_name = StringField()
    departments = ListField(StringField())


class ProfessorVector(Document):
    meta = {'collection': 'professorVectors'}
    vec = ListField(FloatField())
    metadata = EmbeddedDocumentField(ProfessorMetadata)

And setup a schema like so

class Query(graphene.ObjectType):
    professor_vector = graphene.Field(ProfessorVector, id=graphene.String())

    def resolve_professor_vector(self, info, id):
        print(id)
        return ProfessorVectorModel.objects(metadata__id=id).first()

schema = graphene.Schema(query=Query, types=[ProfessorVector])

And a sample of the object in the database:

{ 
    "_id" : ObjectId("5b0c4c9628086740a872dd4d"), 
    "id" : "5e06aa20-6805-4eef-a144-5615dedbe32b", 
    "vec" : [
        -1.9864423274993896, 
        -0.6392910480499268
    ], 
    "metadata" : {
        "id" : "5e06aa20-6805-4eef-a144-5615dedbe32b", 
        "first_name" : "Nigel S", 
        "last_name" : "Paneth", 
        "departments" : [
            "Epidemiology and Biostatistics"
        ]
    }
}

However with this setup, I am not able to run the following query in graphiql:

query {
  professorVector(id: "5e06aa20-6805-4eef-a144-5615dedbe32b") {
    id
    vec
    metadata {
      first_name
    }
  }
}

I get the following error:

{
  "errors": [
    {
      "message": "Cannot query field \"metadata\" on type \"ProfessorVector\".",
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ]
    }
  ]
}

What's the issue with EmbeddedDocumentField? Does graphene-mongo support this kind of embedded document? Any help would be appreciated.

Thanks.

Bug in version 0.2.0 all_posts = MongoengineConnectionField(Posts)

After updating graphene-mongo from 0.18 to 0.2.0, I could not get "ListField(StringField())" types in query parameter. So I revert back to 0.18.
For example:

query{
allPosts(here I can have all fields of posts collection except 'selectedtags' and 'postpics')
}

in post_model.py I have following
class Posts(Document): meta = {'collection': 'posts'} categoryid = ReferenceField(Categories) cityid = ReferenceField(City) pdate = DateTimeField() content = DictField(required = True) selectedtags = ListField(StringField()) postpics = ListField(StringField()) featured = BooleanField()

Cannot connect to Mongo: Error: You have not defined a default connection

Hello Folks.

I tried graphene with SQLite and works like a charm, but now I am trying to use mongo DB using this project graphql-python/graphene-mongo.

What I did was install the following packages

pip install "graphene-django>=2.0"
pip install graphene-mongo
pip install djongo

And include the following configuration in my settings.py

DATABASES = {
    'default': {
        'ENGINE': 'djongo',
        'ENFORCE_SCHEMA': False,
        'NAME': 'mymongodb',
        'HOST': 'localhost',
        'PORT': 3501,
    }
}

Django connects perfectly because I can run migration and new collection was created in my new Mongo (empty) database using the following command.

python manage.py migrate

In addition, I could create a super user, with the instruction.

python manage.py createsuperuser

The admin user works like a charm.

I copy the model and schema from the example https://github.com/graphql-python/graphene-mongo/tree/master/examples/flask_mongoengine and is accesible via documenation in Graphiql.

But when I tried to run the following query.

{
  allEmployees {
    edges {
      node {
        id,
        name,
        department {
          id,
          name
        },
        roles {
          edges {
            node {
              id,
              name
            }
          }
        },
        leader {
          id,
          name
        }
        tasks {
          edges {
            node {
              name,
              deadline
            }
          }
        }
      }
    }
  }
}

I got the error

{
  "errors": [
    {
      "message": "You have not defined a default connection",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "allEmployees"
      ]
    }
  ],
  "data": {
    "allEmployees": null
  }
}

Reference resolving for federation support

Hello,
I am currently working on a project where I use the Apollo Federation Gateway and graphene-federation to connect multiple Microservices. For each external Type I need to add the following resolver to the type.

def __resolve_reference(self, info, **kwargs):
    return MY_MODEL.objects.get(id=self.id)

Couldn't we just add something like the following to the MongoEngineObjectType?

@classmethod
def __resolve_reference(cls, info):
    return cls._meta.model.objects.get(id=self.id)

The only problem is that we need to resolve the reference by the Types key which isn't alwasy the id (just in my case). It could be any property of the model.

Thanks!

Filter like mongodb find

Dear,

Is it possible to move the following find to a GraphQL query?

Example Mongodb: (Specifically { date_ar: { $regex: /23:55$/ } )

ยดยดยด

db.collection.find( { date_ar: { $regex: /23:55$/ } } ).limit(30).sort( { $natural: -1 } ).pretty()
{
"_id" : ObjectId("5cb9388e87200745fba9d727"),
"dm3rf" : "4240.02",
"dm3pr" : "4945.75",
"rdm3p" : "40.67",
"unit" : "m3",
"dm3" : "3813.85",
"truckm" : "477",
"date_ar" : "2019-04-18 23:55",
"rdm3" : "1993.03",
"dm3rf_p" : "86.53",
"dm3_p" : "77.83",
"date_utc" : "2019-04-19 02:55:10.030226"
}
{
"_id" : ObjectId("5cb7e7118720071491f42149"),
"dm3pr" : "5623.5",
"date_ar" : "2019-04-17 23:55",
"dm3" : "4265.61",
"dm3_p" : "87.05",
"rdm3" : "3986.05",
"date_utc" : "2019-04-18 02:55:13.017360",
"truckm" : "533",
"dm3rf" : "4353.11",
"dm3rf_p" : "88.84",
"unit" : "m3",
"rdm3p" : "81.35"
}
ยดยดยด

Example GraphQL - (I can only select a specific day and time)

ยดยดยด
allDaym3(dateAr: "2019-04-16 23:55") {
edges {
node {
dm3
dm3rf
rdm3
dm3pr
dateAr
}
}
}
ยดยดยด
ยดยดยด
{
"data": {
"allDaym3": {
"edges": [
{
"node": {
"dm3": "4196.06",
"dm3rf": "4192.95",
"rdm3": "3986.05",
"dm3pr": "5695.5",
"dateAr": "2019-04-16 23:55"
}
}
]
},
ยดยดยด

Please explain the is_filterable logic

I'm confused by this code:

def field_args(self):
def is_filterable(kv):
return hasattr(kv[1], '_type') \
and callable(getattr(kv[1]._type, '_of_type', None))

It seems to me that this test is basically just seeing if the field is a graphene structure (https://github.com/graphql-python/graphene/blob/43aec720a851a7a8e9d3e3a06b96b9a61232a5e7/graphene/types/structures.py), or, really, a NonNull.

Is there a reason filter is restricted to non-null fields? Mongo can query against nulls and missing keys / values.

graphene-mongo + graphene-federation

Hello!

I have created a working example with graphene-mongo and graphene-federation (https://github.com/erebus1/graphene-federation).
We are currently working with GraphQL + Mongoengine. We split up into Microservices, so Federation was the thing to do. After fiddling around for a day, I was able to create a working example with Mongoengine. I want to share it, just in case somebody needs it too.

1. Overview

The example is made up of two Services. Each Service has its own DB. We want to be able to connect these to DBs, but have access via 1 GraphQL Endpoint to be able to connect multiple Frontend Applications. I choose the following file-structure:

gateway
  |-gateway.js
graphene_federation
services
  |-models
    |-ReviewModel.py
    |-UserModel.py
  |-service1
    |-app.py
    |-config.py
    |-schema.py
    |-server.py
  |-service2
    |-app.py
    |-config.py
    |-schema.py
    |-server.py

2. Models

We have two DB-Models, a Userand a Review. Each of the models is sitting in its own Database. They are only connected via Mongoengine.

models/ReviewModel.py
from mongoengine import Document
from mongoengine.fields import StringField, ReferenceField

from .UserModel import UserModel


class ReviewModel(Document):
    name = StringField()
    user = ReferenceField(UserModel)
    meta = {'db_alias': 'review-db'}
models/UserModel.py
from mongoengine import Document
from mongoengine.fields import StringField, IntField


class UserModel(Document):
    username = StringField()
    age = IntField()
    meta = {'db_alias': 'user-db'}

3. Services

I wasn't very creative on the service. I just called them service1 and service2. Service1 is handling the users and Service2 the reviews.
Both have the same structure:

serviceX
|
|-app.py
|-config.py
|-schema.py
|-server.py
server.py

The server.py file is the same in both services, except for the port to avoid conflicts. I am using a UploadView. We are using File uploads, so this a custom option.

from app import app
from graphene_file_upload.flask import FileUploadGraphQLView
from mongoengine import connect
from schema import schema

# we need to connect to both databases
connect('service1',
        host=app.config['MONGO_HOST'],
        alias='user-db',
        username=app.config['MONGO_USER'],
        password=app.config['MONGO_PWD'],
        authentication_source="admin")

connect('service2',
        host=app.config['MONGO_HOST'],
        alias='review-db',
        username=app.config['MONGO_USER'],
        password=app.config['MONGO_PWD'],
        authentication_source="admin")

app.add_url_rule('/graphql', view_func=FileUploadGraphQLView.as_view('graphql', schema=schema, graphiql=app.debug))

if __name__ == '__main__':
    app.run(port=5000)
app.py

The app.py is even simpler.

from flask import Flask

app = Flask(__name__)
app.debug = True

app.config.from_object('config.DevConfig')
config.py

Nothing special here. Fill in your own details.

class BaseConfig:
    TESTING = False
    DEBUG = False

    MONGO_USER = '******'
    MONGO_PWD = '*****'

    MONGO_HOST = '*******'


class DevConfig(BaseConfig):
    DEBUG = True
schema.py

The schema file is the big difference in the two services. Let's start by the User.

service1
import graphene
from graphene_mongo import MongoengineObjectType

from graphene_federation import build_schema, key
from models.UserModel import UserModel


# The primary key 
@key('id')
class User(MongoengineObjectType):

    # we resolve a db reference by the id
    def __resolve_reference(self, info, **kwargs):
        # common mongoengine query
        return UserModel.objects.get(id=self.id)

    # Use Model as Type (common graphene_mongoengine)
    class Meta:
        model = UserModel


# define a query
class Query(graphene.ObjectType):
    users = graphene.Field(User)

    def resolve_users(self, info, **kwargs):
        return UserModel.objects.all()


# define a mutation
class CreateUser(graphene.Mutation):
    user = graphene.Field(User)

    class Arguments:
        username = graphene.String()
        age = graphene.Int()

    def mutate(self, info, username, age):
        user = UserModel(username=username, age=age)
        user.save()
        return CreateUser(user)


class Mutation(graphene.ObjectType):
    create_user = CreateUser.Field()


# build schema USE THE FEDERATION PACKAGE
schema = build_schema(Query, types=[User], mutation=Mutation)

As you can see, nothing special happens here. We just need to set the id as our key and add a resolver for the key.
Service2 will now be able to resolve a type from another schema by using the external function.

service2
import graphene
from graphene_mongo import MongoengineObjectType

from graphene_federation import build_schema, key, external, extend
from models.ReviewModel import ReviewModel


# use extend and the key to tell service2 that this a type from another service
@extend('id')
class User(graphene.ObjectType):
    # define key, use graphene.ID because this is the type used by graphene_mongo
    # set it to external
    id = external(graphene.ID())


# set id as key
@key('id')
class Review(MongoengineObjectType):
    # Add user as type
    user = graphene.Field(User)

    # optional: we dont need to resolve the reference
    def __resolve_reference(self, info, **kwargs):
        return ReviewModel.objects.get(id=self.id)

    # Use Model as Type (common graphene_mongoengine)
    class Meta:
        model = ReviewModel


# define a query
class Query(graphene.ObjectType):
    reviews = graphene.Field(Review)

    def resolve_reviews(self, info, **kwargs):
        return ReviewModel.objects.all().first()


# define a mutation
class CreateReview(graphene.Mutation):
    review = graphene.Field(Review)

    class Arguments:
        name = graphene.String()
        user_id = graphene.String()

    def mutate(self, info, name, user_id):
        review = ReviewModel(name=name, user=user_id)
        review.save()
        return CreateReview(review)


class Mutation(graphene.ObjectType):
    create_review = CreateReview.Field()


# build schema USE THE FEDERATION PACKAGE
schema = build_schema(Query, mutation=Mutation)

4. Gateway

To combine our 2 Services into one gateway, we use the Apollo Gateway.
We just need to create the following file:

gateway.js
const { ApolloServer } = require('apollo-server');
const { ApolloGateway } = require("@apollo/gateway");

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'service1', url: 'http://127.0.0.1:5000/graphql' },
    { name: 'service2', url: 'http://127.0.0.1:5001/graphql' },
    // more services
  ],
});

const server = new ApolloServer({
  gateway,

  // Currently, subscriptions are enabled by default with Apollo Server, however,
  // subscriptions are not compatible with the gateway.  We hope to resolve this
  // limitation in future versions of Apollo Server.  Please reach out to us on
  // https://spectrum.chat/apollo/apollo-server if this is critical to your adoption!
  subscriptions: false,
});

server.listen().then(({ url }) => {
  console.log(`๐Ÿš€ Server ready at ${url}`);
});

Next we need to install the depencies:

npm install @apollo/gateway apollo-server graphql

5. Start it up

The last thing is to just run the two flask applications and start the gateway via
node gateway.js

I hope all the instructions are clear enough. Have a great day!!!

Filters for Nested Documents Via Relay Does Not Filter Properly

Filtering works for a base document to a query. But when filtering by a property for a nested document, the query returns all results regardless of the argument value.

Example.

Schema.py

class Subgraph(MongoengineObjectType):


    class Meta:
        model = SubgraphModel
        interfaces = (RelayNode,)


class Graph(MongoengineObjectType):

    class Meta:
        model = GraphModel
        interfaces = (RelayNode,)

class Query(graphene.ObjectType):

    node = RelayNode.Field()
    graph = RelayNode.Field(Graph)
    graphs = MongoengineConnectionField(Graph)

Model.py

class Subgraph(Document):
    meta = {'collection': 'subgraph'}
    key = StringField(required=True)

class Graph(Document):
    meta = {'collection': 'graph'}
    key = StringField(required=True)
    subgraphs = ListField(ReferenceField(Subgraph), required=True)

Query that fails:

{
 graphs(key:"graph1") {
  edges {
    node {
      id
      subgraphs(key:"none") {
        edges {
          node {
            id
            key
          }
        }
      }
    }
  }
}
}

The first argument to graphs filters properly. But the second argument does not filter at all, because all results are returned.

Result:

{
  "data": {
    "graphs": {
      "edges": [
        {
          "node": {
            "id": "R3JhcGg6NWI3NWU1YWYxMGYyMGMxZDliYmZmYzBj",
            "key": "graph1",
            "subgraphs": {
              "edges": [
                {
                  "node": {
                    "id": "U3ViZ3JhcGg6NWI3NWU1YWYxMGYyMGMxZDliYmZmYzAw",
                    "key": "subgraph1"
                  }
                },
                {
                  "node": {
                    "id": "U3ViZ3JhcGg6NWI3NWU1YWYxMGYyMGMxZDliYmZmYzAx",
                    "key": "subgraph2"
                  }
                },
                {
                  "node": {
                    "id": "U3ViZ3JhcGg6NWI3NWU1YWYxMGYyMGMxZDliYmZmYzAy",
                    "key": "subgraph3"
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
}

Here is the produced schema

schema {
  query: Query
}

type Graph implements Node {
  id: ID!
  key: String!
  subgraphs(before: String, after: String, first: Int, last: Int, id: ID, key: String): SubgraphConnection
}

type GraphConnection {
  pageInfo: PageInfo!
  edges: [GraphEdge]!
}

type GraphEdge {
  node: Graph
  cursor: String!
}

interface Node {
  id: ID!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type Query {
  node(id: ID!): Node
  graph(id: ID!): Graph
  graphs(before: String, after: String, first: Int, last: Int,  id: ID, key: String): GraphConnection
}

type Subgraph implements Node {
  id: ID!
  key: String!
}

type SubgraphConnection {
  pageInfo: PageInfo!
  edges: [SubgraphEdge]!
}

type SubgraphEdge {
  node: Subgraph
  cursor: String!
}

update mutation on nested document fails

Hi, I'm trying to update a nested document including an EmbeddedDocumentListField / EmbeddedDocument with graphene-mongo. Creating a new user via create mutation works perfectly fine, but when I try to update a nested document, it fails with the error
Invalid embedded document instance provided to an EmbeddedDocumentField: ['label']

Here is my code:
models.py:

from mongoengine import Document, EmbeddedDocumentListField, EmbeddedDocument
from mongoengine.fields import StringField


class UserLabel(EmbeddedDocument):
    code = StringField()
    value = StringField()


class User(Document):
    meta = {'collection': 'user'}
    first_name = StringField(required=True)
    last_name = StringField(required=True)
    label = EmbeddedDocumentListField(UserLabel)

app.py:

from flask import Flask
from flask_graphql import GraphQLView
import graphene
from graphene_mongo import MongoengineObjectType
from mongoengine import connect
from models import User as UserModel, UserLabel as UserLabelModel

app = Flask(__name__)

class UserLabel(MongoengineObjectType):
    class Meta:
        model = UserLabelModel


class User(MongoengineObjectType):
    class Meta:
        model = UserModel


class UserLabelInput(graphene.InputObjectType):
    code = graphene.String()
    value = graphene.String()


class UserInput(graphene.InputObjectType):
    id = graphene.String()
    first_name = graphene.String()
    last_name = graphene.String()
    label = graphene.List(UserLabelInput, required=False)


class Query(graphene.ObjectType):
    users = graphene.List(User)

    def resolve_users(self, info):
        return list(UserModel.objects.all())


class createUser(graphene.Mutation):
    user = graphene.Field(User)

    class Arguments:
        user_data = UserInput()

    def mutate(root, info, user_data):
        user = UserModel(
            first_name=user_data.first_name,
            last_name=user_data.last_name,
            label=user_data.label
        )
        user.save()
        return createUser(user=user)


class updateUser(graphene.Mutation):
    user = graphene.Field(User)

    class Arguments:
        user_data = UserInput()

    def mutate(self, info, user_data):
        user = UserModel.objects.get(id=user_data.id)

        if user_data.first_name:
            user.first_name = user_data.first_name
        if user_data.last_name:
            user.last_name = user_data.last_name
        if user_data.label:
            user.label = user_data.label

        user.save()
        return updateUser(user=user)


class Mutation(graphene.ObjectType):
    create_user = createUser.Field()
    update_user = updateUser.Field()


schema = graphene.Schema(query=Query, mutation=Mutation)
app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True))

if __name__ == '__main__':
    app.run(debug=True, port=1234)

Trying to run this update mutation via graphiql :

mutation {
  updateUser(userData: {
    id: "5d6f8bbbe3ec841d93229322",
    firstName: "Peter",
    lastName: "Simpson",
    label: [
      {
        code:"DE",
        value: "Peter Simpson"
      }
    ]
  }) {
    user {
      id
      firstName
      lastName
      label {
        code
        value
      }
    }
  }
}

I get the error:

{
  "errors": [
    {
      "message": "ValidationError (User:5d6f8bbbe3ec841d93229322) (Invalid embedded document instance provided to an EmbeddedDocumentField: ['label'])",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "updateUser"
      ]
    }
  ],
  "data": {
    "updateUser": null
  }
} 

Updating without the nested field works perfectly fine.
How can I resolve this ?

How to achieve filtering?

Hi,

It would be good if graphene mongo had support for filters. For example
query { allPersons(filter: { age_gt: 18 }) { firstName lastName } }
Is there any other way to achieve this or fire a query like thin in graphene-mongo?

Does not support PointField

model using PointField raises an error
Exception: Don't know how to convert the MongoEngine field <mongoengine.fields.PointField object at 0x04365090> (<class 'mongoengine.fields.PointField'>)

Types are unaware of parent class attributes defined on child model

First of all, thank you for writing this library. I've been wanting to try GraphQL out with my current project but didn't want to have to create an entire new backend application from scratch. I can reuse my existing models thanks to this library, way cool ๐Ÿ‘ ๐Ÿฅ‡

Now for my issue...

I have a parent/child relationship defined like this:

from mongoengine import Document

class Parent(Document):
    bar = StringField()

class Child(Parent):
    baz = StringField()

When I defined my schema and attempt to query against the Child model, it says Unknown argument "bar" on field "child" of type "Query"

My query:

{
  child(bar:"a valid value") {
    edges {
      node {
        bar
        baz
      }
    }
  }
}
from graphene_mongo import MongoengineConnectionField, MongoengineObjectType
from app.models import Child as ChildModel

class Child(MongoengineObjectType):
    class Meta:
        model = ChildModel
        interfaces = (Node,)

class Query(graphene.ObjectType):
    node = Node.Field()
    child = MongoengineConnectionField(Child)

    schema = graphene.Schema(query=Query, types=[Child])

I may just be misusing the library, or perhaps this is a feature that isn't implemented yet. If the feature hasn't been implemented yet I am up for taking a stab at it. Is there a way for my schema to infer the parent's attributes based on how I define them like the above example? Thank you again!

Support for SequenceField

Hello everybody,
we are using the SequenceField to create Invoice Numbers. Sadly there is no integration. I get the following error:

Exception: Don't know how to convert the MongoEngine field <mongoengine.fields.SequenceField object at 0x10df7a908> (<class 'mongoengine.fields.SequenceField'>)

Is it possible to add the support? Thank you in advance :)

The one2many relationship would return all records in db if no specified

models

class Article(Document):

    meta = {'collection': 'test_article'}
    headline = StringField(required=True)
    pub_date = DateTimeField(default=datetime.now)


class Player(Document):

    first_name = StringField(required=True)
    last_name = StringField(required=True)
    opponent = ReferenceField('Player')
    articles = ListField(ReferenceField('Article'))

fixture

    article1 = Article(headline='Hello', editor=editor1)
    article1.save()
    article2 = Article(headline='World', editor=editor2)
    article2.save()

    player1 = Player(first_name='Michael', last_name='Jordan')
    player1.save()

query

    class Query(graphene.ObjectType):

        all_players = MongoengineConnectionField(PlayerNode)

    query = '''
        query PlayersQuery {
            allPlayers {
                edges {
                    node {
                        firstName,
                        allArticles {
                            edges {
                                node {
                                    headline
                                }
                            }
                        }
                    }
                }
            }
        }
  '''

Now it returns all articles from db under Michael Jordan player node :(

Can't query by id?

Thanks for a great library.

After starting examples/flask_mongoengine :
I expected I would be able to query by id?
I'm unable to query by id, but can query by name

{
  allEmployees(id:"RW1wbG95ZWU6NWE5ZjE4NzA2MGJmZmVhZDcyYmI3Zjgw") {
    pageInfo {
      hasNextPage
    }
    edges {
      node {
        id, name        
        department {
          id, name
        }
      }
    }
  }
}


{
  "errors": [
    {
      "message": "u'RW1wbG95ZWU6NWE5ZjE4NzA2MGJmZmVhZDcyYmI3Zjgw' is not a valid ObjectId, it must be a 12-byte input or a 24-character hex string",
      "locations": [
        {
          "column": 3,
          "line": 13
        }
      ]
    }
  ],
  "data": {
    "allEmployees": null
  }
}

{
  allEmployees( name: "Peter") {
    pageInfo {
      hasNextPage
    }
    edges {
      node {
        id, name        
        department {
          id, name
        }
      }
    }
  }
}

{
  "data": {
    "allEmployees": {
      "pageInfo": {
        "hasNextPage": false
      },
      "edges": [
        {
          "node": {
            "id": "RW1wbG95ZWU6NWE5ZjE4NzA2MGJmZmVhZDcyYmI3Zjgw",
            "name": "Peter",
            "department": {
              "id": "RGVwYXJ0bWVudDo1YTlmMTg3MDYwYmZmZWFkNzJiYjdmN2M=",
              "name": "Engineering"
            }
          }
        }
      ]
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.