Default value for custom input object type
I have a custom input object type
class Filters(graphene.InputObjectType):
name = graphene.String()
type = graphene.List(graphene.String)
and the query definition
class Query(graphene.ObjectType):
search = graphene.Field(
SearchResult, query=graphene.String(required=True),
filters=graphene.Argument(Filters, default_value=None)
)
I want to be able to provide an empty default value for the filters argument. With the above definition, the argument is not set as optional and I'm getting this error message:
{
"errors": [
{
"message": "resolve_search() missing 1 required positional argument: 'filters'",
"locations": [
{
"line": 15,
"column": 3
}
],
"path": [
"search"
]
}
],
"data": {
"search": null
}
}
What is the correct way to provide an empty default value for the argument of custom object type? I'm at these package versions:
graphene==2.1.3
graphql-core==2.1
graphql-server-core==1.1.1
I usually do it in the resolver:
class MyQuery(...):
...
def resolver(root, info, filters=ABC):
pass
does this help? :)
Not really, for two reasons. When I define default value in the resolver, it's not advertised in a schema, for example in GraphiQL I don't see the default value for filters argument. That's why I specify the default value in field definition. The second reason is that I specifically don't know how to define an empty value of the Filters type. If I try to use filters=Filters() I get this error:
{
"errors": [
{
"message": "argument of type 'List' is not iterable",
"locations": [
{
"line": 15,
"column": 3
}
],
"path": [
"search"
]
}
],
"data": {
"search": null
}
}
When I try to use empty dict as the default value, I get this error:
{
"errors": [
{
"message": "'dict' object has no attribute 'project'",
"locations": [
{
"line": 15,
"column": 3
}
],
"path": [
"search"
]
}
],
"data": {
"search": null
}
}
However, when I query the schema like this
query Search {
search(query: "some text", filters: {}) {
count
}
}
I get the data without error.
After a lot of experimenting, I've found a workaround. If I define class
class DefaultFilters(object):
name = None
type = None
I can then use its instance as a default value in field definition:
class Query(graphene.ObjectType):
search = graphene.Field(
SearchResult, query=graphene.String(required=True),
filters=Filters(default_value=DefaultFilters)
)
Although this works, I don't believe this is the correct way to achive the goal.
Yeah using Filters() should work, so I guess there's a bug somewhere :)
I'll see if I can have a look at it soon
class Query(graphene.ObjectType):
search = graphene.Field(
SearchResult,
query=graphene.String(required=True),
filters=graphene.Argument(Filters, default_value={})
)
Seems to work and produces
type Query {
search(query: String!, filters: Filters = {}): SearchResult
}
I also tried it with your DefaultFilters but I couldn't output the schema for some reason.
@cherls Yeah, it produces the correct schema (or at least the schema I except and I'm happy with), but the problem is with execution. When I execute the query and don't provide filters argument to search field, I get this error:
{
"errors": [
{
"message": "'dict' object has no attribute 'project'",
"path": [
"search"
],
"locations": [
{
"line": 2,
"column": 3
}
]
}
],
"data": {
"search": null
}
}
Thinking about it, I guess Graphene is just about schema creation and not concerned with execution, right?
@tlinhart
Execution works fine for me with the following code. Note that the filters argument is missing on the search field. I think the error is something else in your code.
import json
import graphene
class SearchResult(graphene.ObjectType):
result = graphene.String()
class Filters(graphene.InputObjectType):
name = graphene.String()
type = graphene.List(graphene.String)
class Query(graphene.ObjectType):
search = graphene.Field(
SearchResult,
query=graphene.String(required=True),
filters=graphene.Argument(Filters, default_value={})
)
def resolve_search(self, info, query, filters):
return SearchResult(result="result for " + query)
schema = graphene.Schema(query=Query)
result = schema.execute(
'''
query {
search (query: "test query") {
result
}
}
'''
)
print(json.dumps(result.data))
with open('schema.graphql', 'w') as fp:
fp.write(str(schema))
I don't execute the query directly, I use run_http_query and encode_execution_results functions from graphql-server-core package. But I'm pretty sure that's not the issue.
After some debugging, I think I found the source of the problem. Inside the resolver, I work with filters argument as of type Filters i.e. I use dotted notation to access the attributes (e.g. filters.type). But when I define the default value as {} and don't supply the value in a query, inside the resolver filters is of type dict and thus I receive the 'dict' object has no attribute 'type' error message during execution. When I provide a value in the query (e.g. search(query: "some query", filters: {type: TYPE1}) { result } where TYPE1 is an enum value), inside the resolver filters is of type Filters and dotted notation works. Also the schema can be dumped using fp.write(str(schema)).
If I define the default value as Filters() (i.e. empty instance), inside the resolver filters is correctly of type Filters. However, if I don't supply a value in a query and try to print the value in the resolver, I get <schema.Filters object at 0x7f6892f544e0> (serialization doesn't work). If I provide a value in the query (e.g. search(query: "some query", filters: {type: TYPE1}) { result }), filters is of type Filters inside the resolver and printing the value correctly outputs {'type': ['type1']}.
Another problem is that with Filters() as a default value, schema cannot be dumped. If I try fp.write(str(schema)), it produces this error:
Traceback (most recent call last):
File "/home/tmp/test.py", line 59, in <module>
fp.write(str(schema))
File "/home/tmp/venv/lib/python3.4/site-packages/graphene/types/schema.py", line 111, in __str__
return print_schema(self)
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 31, in print_schema
schema, lambda n: not (is_spec_directive(n)), _is_defined_type
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 75, in _print_filtered_schema
for typename, type in sorted(schema.get_type_map().items())
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 76, in <listcomp>
if type_filter(typename)
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 108, in _print_type
return _print_object(type)
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 138, in _print_object
type.name, implemented_interfaces, _print_fields(type)
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 174, in _print_fields
for f_name, f in type.fields.items()
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 174, in <genexpr>
for f_name, f in type.fields.items()
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 198, in _print_args
for arg_name, arg in field_or_directives.args.items()
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 198, in <genexpr>
for arg_name, arg in field_or_directives.args.items()
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/schema_printer.py", line 206, in _print_input_value
default_value = " = " + print_ast(ast_from_value(arg.default_value, arg.type))
File "/home/tmp/venv/lib/python3.4/site-packages/graphql/utils/ast_from_value.py", line 55, in ast_from_value
assert isinstance(value, dict)
AssertionError
I naively assumed that this kind of things is handled automatically by Graphene. Is there something that I'm missing?
@cherls Using default_value={} I get the correct schema definition but the value passed eventually to my resolver is the empty dictionary but not my custom input type with default values I was expecting. Am I missing something or is that the intended behavior?
For now, I've ended up with the following solution:
import graphene
class InputObjectType(graphene.InputObjectType):
@classmethod
def default(cls):
meta = cls._meta
fields = meta.fields
default_fields = {name: field.default_value for name, field in fields.items()}
container = meta.container
return container(**default_fields)
class SizeInput(InputObjectType):
width = graphene.Int(default_value=100)
height = graphene.Int(default_value=100)
def square(self):
return self.width * self.height
Since now, it's possible to do:
class Query(graphene.ObjectType):
square = graphene.Field(
graphene.NonNull(graphene.Int),
size=graphene.Argument(SizeInput, default_value=SizeInput.default())
)
@staticmethod
def resolve_square(root, info, size, **kwargs):
return size.square()
It produces the correct schema and passes the default value I was expecting instead of the empty dict.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
class InputObjectType(graphene.InputObjectType): @classmethod def default(cls): meta = cls._meta fields = meta.fields default_fields = {name: field.default_value for name, field in fields.items()} container = meta.container return container(**default_fields)
Brilliant, this should be part of Graphene
Agreed!
Is @artemnesterenko 's comment still the best option to solve this issue in 2024?