I almost spent 3 days to find a way for creating a dynamic schema in python graphene. the only related result I could find is the below link: https://github.com/graphql-python/graphene/blob/master/graphene/types/dynamic.py But I couldn't find any documentation for it.
The whole idea is to create a dynamic schema. I want to provide a GraphQL compatible API that makes users able to query my contents even if Models are not defined in the code. In other words, I want to create Models on the fly. I have no idea about what shall I do.
It would be a great favor if you can provide an example for that.
Update :
My Project is a Headless CMS which has a feature that users can create their own content types and I want to provide a GraphQL interface to make everything easier and more flexible.
Here is example of my Content Types in DB :
{
"id": "author",
"name": "Book Author",
"desc": "",
"options":[
{
"id": "author_faname",
"label": "Sample Sample",
"type": "text",
"required": true,
"placeholder":"One Two Three Four"
},
{
"id": "author_enname",
"label": "Sample label",
"type": "text",
"required": true,
"placeholder":"Sample Placeholder"
}
]
}
And Here is Stored content in DB based on that content type :
{
"id": "9rqgbrox10",
"content_type": "author",
"data":{
"author_fname":"Jimmy",
"author_ename":"Hello"
}
}
Now as my Models are not declared in Code and they are completely in DB, I want to make my schemas on the fly and I don't know what is best the solution for this. I know there should be a way because the other Headless CMS Projects are providing this.
Thanks in advance!
Basically, schema is created something like this:
class MyType(graphene.ObjectType):
something = graphene.String()
class Query(graphene.ObjectType):
value = graphene.Field(MyType)
schema = graphene.Schema(query=Query, types=[MyType])
First, in order to add some kind of dynamics, you will most likely want to wrap the above code in a function like create_schema()
.
Then, when you want to dynamically create a class during runtime, the above code can be rewritten like this:
def create_schema():
MyType = type('MyType', (graphene.ObjectType,), {
'something': graphene.String(),
})
Query = type('Query', (graphene.ObjectType,), {
'value': graphene.Field(MyType),
})
return graphene.Schema(query=Query, types=[MyType])
For your example it could look something like this:
def make_resolver(record_name, record_cls):
def resolver(self, info):
data = ...
return record_cls(...)
resolver.__name__ = 'resolve_%s' % record_name
return resolver
def create_schema(db):
record_schemas = {}
for record_type in db.get_record_types():
classname = record_type['id'].title() # 'Author'
fields = {}
for option in record_type['options']:
field_type = {
'text': graphene.String,
...
}[option['type']
fields[option['id']] = field_type() # maybe add label as description?
rec_cls = type(
classname,
(graphene.ObjectType,),
fields,
name=record_type['name'],
description=record_type['desc'],
)
record_schemas[record_type['id']] = rec_cls
# create Query in similar way
fields = {}
for key, rec in record_schemas:
fields[key] = graphene.Field(rec)
fields['resolve_%s' % key] = make_resolver(key, rec)
Query = type('Query', (graphene.ObjectType,), fields)
return graphene.Schema(query=Query, types=list(record_schemas.values()))
Note that if you try to insert new fields into already existing class,
like this - MyType.another_field = graphene.String()
,
then it won't work: that is because when graphene.ObjectType
class is instantiated,
all its fields are recorded in self._meta.fields
OrderedDict.
And updating it is not as straightforward as just MyType._meta.fields['another_field'] = thefield
- see the code of graphene.ObjectType.__init_subclass_with_meta__
for details.
So if your schema is dynamically changed then it might be better to fully re-create it from scratch than to patch it.