Frappe Graphql?

hi
i was trying to implement graphql with frappe
i used graphene python . and i have to resolve frappe objects in Query . am trying to find a dynamic way to do it like in graphene-django or graphene-flask so any guides or help ?
if any one interested we can together and push it to frappe ?

@ahmadRagheb Nice Idea, What is your use case?

using graphql instead of apis call

Are you using JS implementation of graphql?

Where are you actually stuck? Try to generate dictionary of the arguments passed to whitelisted method, you get from graphql and use them as parameters, then return the result.

i have done this so far … am using python graphene
what i have problem with is i have to defiend a class for each doctype i want to resolve like todo for example

class TodoGraphene(graphene.ObjectType):

name = graphene.String()
color = graphene.String()
status = graphene.String()
date = graphene.Date()

def resolve_name(parent, info):
    return parent.get("name")

def resolve_color(parent, info):
    return parent.get("color")

def resolve_status(parent, info):
    return parent.get("status")

def resolve_date(parent, info):
    return parent.get("date")

and this is the query :

getdoc = Field(TodoGraphene, doctype=graphene.String(required=True), name=graphene.String(required=True))

def resolve_getdoc(parent, info, **kwargs):
    if kwargs.get("doctype") and kwargs.get("name"):
        doc = frappe.get_doc(kwargs.get("doctype"), kwargs.get("name"))
        fields = [f.fieldname for f in doc.meta.fields]  
        fields.append('name')
        result = {}
        for field in fields:
            result[field]=doc.get(str(field))
        return result

i want TodoGraphene class to be dynmic … i just pass the doctype name and it get all the fields and resolve them , like definded on class and apply to all frappe erpnext docs

can you share your code with me it might help or get me to the right path

This has nothing to do with GraphQL, but you could use doc.as_dict() or doc.as_json() to save you some lines:

def resolve_getdoc(parent, info, doctype, name, **kwargs):
    doc = frappe.get_doc(doctype, name)
    return doc.as_dict()
1 Like

thanks @rmeyer this save lots of lines
how do you defiend query ?
getdoc = Field(TodoGraphene, doctype=graphene.String(required=True), name=graphene.String(required=True))

am using a class TodoGraphene and one by one defined type and resolve them, do you use different way ?

Why not use meta-programming?


frappe_to_graphene = {
    "Date": graphene.Date
}

def get_graphene_class(doctype, suffix='Graphene'):
    from frappe.models import no_value_fields

    docfields = frappe.get_all('DocField', fields=['fieldname', 'fieldtype', filters={'parent': doctype}
    docfields.extend(frappe.get_all('Custom Field', fields=['fieldname': 'fieldtype'], filters={'dt': doctype}))

    attrs={
       df.fieldname: frappe_to_graphene.get(df.fieldtype, graphene.String)()
       for df in docfields
       if df.fieldtype not in no_value_fields
    }
    def cls_resolve_get_doc(self, parent, info, name, **kwargs)
          return resolve_get_doc(parent, info, doctype, **kwargs)
    cls_resolve_get_doc.__name__ = "resolve_get_doc"

    attrs["resolve_get_doc"] = cls_resolve_get_doc

    return type(doctype.replace(' ','').strip() + suffix, (graphene.ObjectType,), attrs)

graphene_doctype = graphene.String(required=True)
graphene_name=graphene.String(required=True)

def get_graphene_get_doc(doctype):
    return graphene.Field(get_graphene_class(doctype), doctype=graphene_doctype, name=graphene_name)

def resolve_get_doc(parent, info, doctype, name, **kwargs):
    doc = frappe.get_doc(doctype, name)
    return doc.as_dict()

ToDoGraphene = get_graphene_class('ToDo')

For sure there’s various places for improvements, but this define the minimal mechanics for class generation on run-time, using the type function in in Python

2 Likes

i will try to rewrite it as a class FrappeObjectType so i can pass meta to it and use it like django does for defending classes

class User(DjangoObjectType):
class Meta:
model = UserModel
fields = (‘id’, ‘question_text’)
exclude = (‘question_text’,)
extra_field = graphene.String()

def resolve_extra_field(self, info):
    return 'hello!'

Hi,

It still need a lot of work but we might be able to do this.

https://github.com/frappe/frappe/pull/11339

3 Likes

i message you we can work together on this

Hey @pipech did you continue working on this after they rejected your PR? the erpnext “REST” API is very weird (not very good). For example, it’s common to get a bunch of HTML and even javascript in error messages.

A clean & normal graphql API would be a huge benefit to users and help increase adoption.

4 Likes