graphql-python / graphene Goto Github PK
View Code? Open in Web Editor NEWGraphQL framework for Python
Home Page: http://graphene-python.org/
License: MIT License
GraphQL framework for Python
Home Page: http://graphene-python.org/
License: MIT License
It would be great to have a test/example that shows how args on e.g. a StringField
work. Right now I am getting an error AssertionError: Client.name(test:) argument type must be Input Type but got: ....
I have tried to use InputObjectType
but there are very few tests on that and very little documentation (in the source code)
Also, now that I am starting to understand graphql, relay and graphene better I would be more than up to add some more tests or documentation if that would help at all.
I tried to run the following:
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(firstName=graphene.String())
def resolve_hello(self, args, info):
return 'Hello ' + args.get('firstName')
schema = graphene.Schema(query=Query)
result = schema.execute("""
query foo($firstName:String)
{
hello(firstName:$firstName)
}
""", args={"firstName": "Serkan"})
print result.data
It works. However, if I change all references to firstName to name, it stops working. Is this documented, or is it a bug?
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String())
def resolve_hello(self, args, info):
return 'Hello ' + args.get('name')
schema = graphene.Schema(query=Query)
result = schema.execute("""
query foo($name:String)
{
hello(name:$name)
}
""", args={"name": "Serkan"})
print result.data
The stack trace is the following:
File "test.py", line 18, in <module>
""", args={"name": "Serkan"})
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphene/core/schema.py", line 122, in execute
kwargs = dict(kwargs, request=request, root=root, args=args, schema=self.schema)
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphene/core/schema.py", line 85, in schema
subscription=self.T(self.subscription))
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphene/core/schema.py", line 22, in __init__
super(GraphQLSchema, self).__init__(*args, **kwargs)
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphql/core/type/schema.py", line 42, in __init__
self._type_map = self._build_type_map()
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphql/core/type/schema.py", line 85, in _build_type_map
type_map = type_map_reducer(type_map, type)
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphql/core/type/schema.py", line 117, in type_map_reducer
field_map = type.get_fields()
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphql/core/type/definition.py", line 193, in get_fields
self._field_map = define_field_map(self, self._fields)
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphql/core/type/definition.py", line 218, in define_field_map
assert_valid_name(field_name)
File "/Users/ustun/Projects/graphene_test/.venv/lib/python2.7/site-packages/graphql/core/type/definition.py", line 758, in assert_valid_name
assert COMPILED_NAME_PATTERN.match(name), 'Names must match /{}/ but "{}" does not.'.format(NAME_PATTERN, name)
TypeError: expected string or buffer
As @jhgg tagged, could be great if we allow custom executor (or custom executor settings) in the schema.
This is probably not the best place to discuss this as it is not a bug report, but I was wondering how we should approach data validation with graphql and graphene, especially with its Django integration.
For example, should graphene replace the django forms layer as well? I believe it should and it can, since its validation capabilities are much richer. It is possible to validate if something is a string in django, but going beyond that, for example, ensuring a value is a list of strings is not possible without custom code.
We should discuss and document the best practices here.
I see graphene as ultimately replacing the urls, views and forms parts of django.
Having something like https://github.com/graphql/graphql-js/blob/master/src/utilities/schemaPrinter.js would be great (for example output see https://facebook.github.io/relay/docs/graphql-relay-specification.html#schema) to get an easier overview of how how your whole schema looks like and fits together, especially when coming to quite complex models.
I didn't find a forced conversion to camelCase in the spec. Did I miss it? Or is graphene just opinionated on style standards. Will there be any way to opt out of this in the future?
Do we have any plan to support google app engine ndb in near future?
If not, I am willing to help.
Could you please guide me to which part I should start to do it?
Thanks
Retrieve fields in Django models efficiently depending on requested fields in the GraphQL query (so DB will be hit in an optimal way).
Thanks @bigblind for the pointing this out!
When I create a query that has a snake case name and attempt to execute it, I get the following error:
[graphql.core.error.GraphQLError('Cannot query field "work_order" on "Query".')]
Here is what my query looks like:
class Query(ObjectType):
work_order = Field(WorkOrder, id=graphene.Argument(graphene.String))
@resolve_only_args
def resolve_work_order(self, id):
return WorkOrder(id='1', state='test')
Here is what my GraphQL query looks like:
query = """
query test {
work_order(id: "1") {
state
}
}
"""
if I change the name of the query to workorder instead of work_order, or workOrder it works just fine. Is this intended behavior?
Use Case: splitting out query definition logic into smaller and more manageable files (e.g. in Django app directories)
Something like this will not currently work:
schema = graphene.Schema(name='Swiftwind Schema')
class Query1(ObjectType):
all_accounts = relay.ConnectionField(accounts_nodes.Account)
account = relay.NodeField(accounts_nodes.Account)
class Query2(ObjectType):
all_transactions = relay.ConnectionField(accounts_nodes.Transaction)
transaction = relay.NodeField(accounts_nodes.Transaction)
class Query3(ObjectType):
all_users = relay.ConnectionField(accounts_nodes.User)
resolve_all_users = ConnectionResolver(accounts_nodes.User)
class Query(Query1, Query2, Query3):
pass
schema.query = Query
It dies with the following error:
File "/Users/adam/Envs/swiftwind/src/graphql-core/graphql/core/type/schema.py", line 30, in __init__
assert isinstance(query, GraphQLObjectType), 'Schema query must be Object Type but got: {}.'.format(query)
I have also tried:
schema = graphene.Schema(name='Swiftwind Schema')
class Query1(object):
all_accounts = relay.ConnectionField(accounts_nodes.Account)
account = relay.NodeField(accounts_nodes.Account)
class Query2(object):
all_transactions = relay.ConnectionField(accounts_nodes.Transaction)
transaction = relay.NodeField(accounts_nodes.Transaction)
class Query3(object):
all_users = relay.ConnectionField(accounts_nodes.User)
resolve_all_users = ConnectionResolver(accounts_nodes.User)
class Query(Query1, Query2, Query3, ObjectType):
pass
schema.query = Query
Which dies with the error:
File "/Users/adam/Envs/swiftwind/src/graphql-core/graphql/core/type/definition.py", line 211, in define_field_map
).format(type)
AssertionError: Query fields must be a mapping (dict / OrderedDict) with field names as keys or a function which returns such a mapping.
Presumably because the fields never get initialised?
Anyway, if this is possible I'd love to know how.
I'm currently running Django 1.6.5, and ran into an error while trying to run graphene:
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
88. response = middleware_method(request)
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/middleware/common.py" in process_request
71. if (not urlresolvers.is_valid_path(request.path_info, urlconf) and
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/core/urlresolvers.py" in is_valid_path
596. resolve(path, urlconf)
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/core/urlresolvers.py" in resolve
476. return get_resolver(urlconf).resolve(path)
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/core/urlresolvers.py" in resolve
337. for pattern in self.url_patterns:
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/core/urlresolvers.py" in url_patterns
365. patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/core/urlresolvers.py" in urlconf_module
360. self._urlconf_module = import_module(self.urlconf_name)
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/django/utils/importlib.py" in import_module
40. __import__(name)
File "/Users/mickiebetz/Code/website/site/src/atlas/urls.py" in <module>
8. from graphene.contrib.django.views import GraphQLView
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/graphene/contrib/django/__init__.py" in <module>
1. from graphene.contrib.django.types import (
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/graphene/contrib/django/types.py" in <module>
6. from graphene.contrib.django.converter import convert_django_field
File "/Users/mickiebetz/Code/website/lib/python2.7/site-packages/graphene/contrib/django/converter.py" in <module>
27. @convert_django_field.register(models.UUIDField)
Exception Type: AttributeError at /graphiql
Exception Value: 'module' object has no attribute 'UUIDField'
The Django UUID model field was added in Django 1.8, yet the setup.py
script notes support for Django versions 1.6 and above.
Would it be possible to remove that field so Graphene is compatible with those versions of Django (1.6 and higher)?
See #51, similarly GraphQL-python-archive/graphql-epoxy#5
Add documentation based on the GraphQL one: http://graphql.org/docs/getting-started/
Also, improve based in other implementation docs. (sangria)
Sections:
I'm porting a graphql server that was created with your graphql-relay js port. I have a mutation that has a list field as an input and works fine, however when I try to create a list field with graphene I get the following error:
AssertionError: NewCellInnerInput.newCells field type must be Input Type but got: [Cell].
Below is a snippet from my ported schema:
class Cell(graphene.ObjectType):
title = graphene.StringField()
item = graphene.StringField()
class NewCell(relay.ClientIDMutation):
class Input:
parent = graphene.IDField(required=True)
newCells = graphene.ListField(Cell)
parent = graphene.Field(Tree)
Add GeoJSON types (graphene.geo
) and the mapping from Django Geo fields to it.
Add flask example using graphene
Currently mutation input type names are hardcoded to <Mutation>InnerInput
I prefer <Mutation>Input
like the types generated in graphql-relay :)
In the Relay mutation docs, performing RANGE_ADD requires that the new edge created by the mutation is provided in the payload.
I do not know how to provide the correct cursor/Edge
construction within the relay.ClientIDMutation
subclass.
The following code I wrote:
new_object_edge = graphene.Field('ObjectEdge')
I believed would solve my issue, with a proper resolve_new_object_edge(...)
resolver.
However, my resolver cannot reference the relay.ConnectionField
that is specified on one of the other object types in my mutation, since it only has the context of my (in this case, Django) objects.
I experimented with relay.Edge.for_node(SomeObjectType)
as well but all of the solutions I have tried so far modify the schema correctly but cannot return the appropriate Edge
.
The implementation of this scenario in javascript can be found here in the Relay examples directory.
Any idea of a best approach? I can take this question to stack overflow, but felt that people using Relay & Graphene would enjoy seeing an example solution in the docs.
It should be useful to have a mongoengine easy integration
I see that when a query is executed via the execute()
method, the result object has errors
- a list of errors. My questions are:
I think that it would be beneficial to be able to pass a custom executor to a Graphene schema, so that it can use it to execute
the query.
I'm thinking either:
schema = Schema(query=QueryRootType, executor=Executor(...))
OR
schema = Schema(query=QueryRootType)
schema.set_executor(Executor(...))
By default it should use the default executor instance. I am going to expose an API for this in core.graphql.execution
from core.graphql import execution
default_executor = execution.get_default_executor()
The default executor will be configured to be a synchronous executor. In addition, you will be able to set the default executor using:
execution.set_default_executor(Executor(...))
For a ConnectionField
we can pass in a a custom edge as so: addresses=relay.ConnectionField(Address, connection_type=AddressType, edge_type=AddressTypeEdge)
. But when the custom edge has some additional fields it seems there is no way for these to be set, as this line: https://github.com/graphql-python/graphql-relay-py/blob/master/graphql_relay/connection/arrayconnection.py#L46 only ever tries to set node
and cursor
. It would be great to have support to resolve some custom fields on a custom edge.
I might look into a fix myself tomorrow since I need that for one of our implementations.
Tried to run tox
but the tests wouldn't load!
(graphene)~/P/graphene git:master ❯❯❯ tox ✱ ◼
GLOB sdist-make: /Users/jake/PycharmProjects/graphene/setup.py
py27 inst-nodeps: /Users/jake/PycharmProjects/graphene/.tox/dist/graphene-0.1.4.zip
py27 installed: blinker==1.4,Django==1.8.5,flake8==2.4.1,graphene==0.1.4,graphql-core==0.1a3,graphql-relay==0.2.0,mccabe==0.3.1,pep8==1.5.7,py==1.4.30,pyflakes==0.8.1,pytest==2.8.2,pytest-django==2.9.1,singledispatch==3.4.0.3,six==1.10.0
py27 runtests: PYTHONHASHSEED='3684720222'
py27 runtests: commands[0] | py.test
Traceback (most recent call last):
File ".tox/py27/bin/py.test", line 11, in <module>
sys.exit(main())
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/config.py", line 38, in main
config = _prepareconfig(args, plugins)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/config.py", line 117, in _prepareconfig
pluginmanager=pluginmanager, args=args)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 724, in __call__
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 338, in _hookexec
return self._inner_hookexec(hook, methods, kwargs)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 333, in <lambda>
_MultiCall(methods, kwargs, hook.spec_opts).execute()
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 595, in execute
return _wrapped_call(hook_impl.function(*args), self.execute)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 249, in _wrapped_call
wrap_controller.send(call_outcome)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/helpconfig.py", line 28, in pytest_cmdline_parse
config = outcome.get_result()
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 279, in get_result
_reraise(*ex) # noqa
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 264, in __init__
self.result = func()
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 596, in execute
res = hook_impl.function(*args)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/config.py", line 852, in pytest_cmdline_parse
self.parse(args)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/config.py", line 957, in parse
self._preparse(args)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/config.py", line 928, in _preparse
args=args, parser=self._parser)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 724, in __call__
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 338, in _hookexec
return self._inner_hookexec(hook, methods, kwargs)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 333, in <lambda>
_MultiCall(methods, kwargs, hook.spec_opts).execute()
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 595, in execute
return _wrapped_call(hook_impl.function(*args), self.execute)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 253, in _wrapped_call
return call_outcome.get_result()
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 279, in get_result
_reraise(*ex) # noqa
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 264, in __init__
self.result = func()
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/_pytest/vendored_packages/pluggy.py", line 596, in execute
res = hook_impl.function(*args)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/pytest_django/plugin.py", line 236, in pytest_load_initial_conftests
settings.DATABASES
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py", line 35, in __exit__
self.gen.throw(type, value, traceback)
File "/Users/jake/PycharmProjects/graphene/.tox/py27/lib/python2.7/site-packages/pytest_django/plugin.py", line 105, in _handle_import_error
raise ImportError(msg)
ImportError: No module named tests.django_settings
pytest-django could not find a Django project (no manage.py file could be found). You must explicitly add your Django project to the Python path to have it picked up.
ERROR: InvocationError: '/Users/jake/PycharmProjects/graphene/.tox/py27/bin/py.test'
First of all great work on these sets of python libraries they really seem great so far :)
Anyway, just trying out graphene 0.1.6.0 (on python 2.7.x) with react/relay for the first time so might be some beginners mistake, But having
class Test(relay.Node):
custom_id = graphene.IDField(description='The id of the test.')
test_name = graphene.StringField(description='The name of the test.')
description = graphene.StringField(description='The description of the test.')
test_type = graphene.StringField(description='What type the test has.')
test_price = graphene.StringField(description='The price of each test.')
test_clients = graphene.StringField(description='Number of test clients.')
@classmethod
def get_node(cls, custom_id):
print('in get_node', custom_id)
return Test(getTest(custom_id))
class Query(graphene.ObjectType):
my_tests = relay.NodeField(Test,
customArg=graphene.Argument(graphene.String))
all_my_tests = relay.ConnectionField(Test,
connection_type=MyConnection,
customArg=graphene.Argument(graphene.String))
node = relay.NodeField()
@resolve_only_args
def resolve_my_tests(self, **kwargs):
print('we are resolving all tests!')
for key in kwargs:
print('kwargs received', key, kwargs[key])
return [Test(t) for t in getTests()]
I am managing to return some data with hand-written queries, but trying a query that react passes, e.g.
result = schema.execute('''
query TestsQuery {
myTests (customArg:"1") {
...__RelayQueryFragment0pgs7aw
}
}
fragment __RelayQueryFragment0pgs7aw on Test{custom_id,description,id}
''')
just throws the error Field "myTests" argument "id" of type "ID!" is required but not provided.
. If I do provide an id
field I get an error that it clashes:
class Test(relay.Node):
File "/usr/local/lib/python2.7/dist-packages/graphene/core/types.py", line 85, in __new__
field_names[field.name].__class__)
Exception: Local field 'id' in class 'Test' (<class 'graphene.relay.fields.GlobalIDField'>) clashes with field with similar name from Interface Node (<class 'graphene.core.fields.StringField'>)
Any idea how to get around/fix this?
Edit: I made some progress using the ConnectionField
but I suppose I am just a bit unsure what the best way forward is with graphene if I want to define a schema where I can show all tests I have but also query them for certain parameters such as name, type etc.
The need for a connection class (with its own fields) with ConnectionField
seems a bit like an extra step I am not sure is necessary for what I am trying to do.
So I suppose what I am imagining is something like
query testsQuery {
allTests(customArg: "1") {
...__RelayQueryFragment0pgs7aw
}
}
fragment __RelayQueryFragment0pgs7aw on Test{custom_id,description, test_name, id}
Where-as right now I am creating on the fronend
query testsQuery{allTests(customArg:"1"){...__RelayQueryFragment0pgs7aw}}
fragment __RelayQueryFragment0pgs7aw on TestMyConnection{
edges{node{custom_id,description, test_name, id},cursor},
pageInfo{hasNextPage,hasPreviousPage
}}
When the name of MyNode in the relay tests: https://github.com/graphql-python/graphene/blob/master/tests/relay/test_relayfields.py#L22 is changed to anything else (and updated here https://github.com/graphql-python/graphene/blob/master/tests/relay/test_relayfields.py#L31) then graphene seems to throw the following error: [GraphQLError('Type MyNode not found in <Schema: Schema (8778058338473)>',)]
Based on a new implementation, is needed to improve the following things for next stable package:
__doc__
enums.Enum
mapping to a GraphQL EnumOne way for deal with most of this improvements is using a TypeRegister as epoxy is doing, however for new types behavior is not as easy to scale.
Having a Type Register means it have to create a different object each time to have to access, for example, an interface (without doing any _add_interface_impl
hacks).
Also, it means the graphql-core
type is fixed for each defined class.
The advantages of having a fixed type your_class.T
is that could be shared across not only this graphene projects, but any that uses graphql-core.
However, the cost of having a fixed type for each defined class are more than the advantages.
Why is not good to have a fixed type for each class? Some classes, like a relay Node
depends a lot on the context where is being executed (or schema/registry). Why create a new Node
class for just changing the context?
Also, it requires to add manually the type into the registry (via mixins, or hacking the registry for automatically adding my type).
Create a schema.T(object_type)
that will return the type for an object_type associated for this schema.
This page has a mutate example, but parameters of the mutate method are incorrect, follow it will get an error: "mutate() takes exactly 3 arguments (4 given)"
According to to this test, it should be like:
@classmethod
def mutate(cls, instance, args, info):
person = Person(name=args.get('name'))
ok = True
return CreatePerson(person=person, ok=ok)
https://github.com/graphql-python/graphene/blob/master/graphene/contrib/django/views.py
Isn't implemented properly. Doesn't do proper error handling & doesn't handle GraphQL variables at all.
I was thinking instead we could remove this from graphene, and make this into it's own standalone package that could be given a GraphQL Schema and create a Django View for it.
That way it could be used with bare graphql-core
, graphql-epoxy
and graphene
.
Thoughts?
graphql-core
supports "tagging" a resolver (via core.execution.middlewares.utils
) as a way to signal special handling of the resolver in an ExecutionMiddleware
.
The main example is that we have GeventExecutionMiddleware
. By default, this middleware does nothing when processing resolvers. But when it is given a resolver that is tagged with (@run_in_greenlet
) it will spawn a greenlet to execute the resolver, and allow the executor to continue to resolve other fields concurrently.
From the tests, here's a good example (not using Graphene):
@run_in_greenlet
def resolve_a(context, *_):
gevent.sleep(3)
return 'resolved a'
@run_in_greenlet
def resolve_b(context, *_):
gevent.sleep(3)
return 'resolved b'
Type = GraphQLObjectType('Type', {
'a': GraphQLField(GraphQLString, resolver=resolver),
'b': GraphQLField(GraphQLString, resolver=resolver_2)
})
executor = Executor(GraphQLSchema(Type), [GeventExecutionMiddleware()])
doc = 'query Example { a, b }'
result = executor.execute(doc)
assert result.data == {'a': 'resolved a', 'b': 'resolved b'}
In this example, if each function was not tagged with @run_in_greenlet
, then the execution time would be 6 seconds, as they have to run serially. However, since they are tagged, the middleware will run them concurrently, executing the query in only 3 seconds.
I've been thinking a bit about Django integration for Graphene. This is in part because I think it could be really useful, and in part because this is something I'd be interested in contributing a pull request for. However, development seems to be moving quickly so I thought it best to discuss these ideas first in case there are already plans.
The main motivation here is reducing boilerplate code. My intention would be to make all automatic functionality overridable & customisable.
Nodes:
resolve_FIELD()
may be specified to customise this traversal, but if not then traversal will be done using the relation's manager's all()
method.resolve_FIELD()
boilerplate code).Query:
ConnectionFields
which connect to DjangoNodes
without the need for a resolve_FIELD()
method.Query
class which inherits from multiple app-specific Query
classes) [related: #55]Mutations:
DjangoMutation
which provides standard functionality though which to update Django models.CreateMutation
, UpdateMutation
, and DeleteMutation
base classes.All/any thoughts very welcome indeed!
Improve syntax.
Add linter to testing, using flake8
Consider this example (adapted from simple example in getting started):
amit@yosemite work $ cat hello_graphql.py
import graphene
class Query(graphene.ObjectType):
hello_id = graphene.String()
def resolve_hello_id(self, args, info):
print 'Resolve called'
return 'World'
schema = graphene.Schema(query=Query)
schema.execute('{ hello_id }')
On executing it, resolve function is not called. I have seen this is a generic issue when the field name is *_id
.
The resolve_type
method on ObjectType
conflicts with actually resolving a field called type
. Given the importance of resolve
method names, I think great care should be taken to make sure graphene's codebase does not use these method names unless necessary.
The below test fails with GraphQLError("'Location' object has no attribute 'T'",)
; renaming the resolve_type
method to _resolve_type
solves the problem.
import graphene
from graphql.core.type import GraphQLEnumValue
LOCATION_TYPES = {
'HQ': "Headquarters",
'BRANCH': "Branch Location",
}
LOCATIONS = {
'1': {
'type': 'HQ',
'address': '123 Main St',
'city': 'Metropolis',
},
'2': {
'type': 'BRANCH',
'address': '55 Side St',
'city': 'Smallville',
},
}
LocationType = graphene.Enum(
'LocationType',
{k: GraphQLEnumValue(value=k, description=v) for k, v in LOCATION_TYPES.items()}
)
class Location(graphene.ObjectType):
type = graphene.NonNull(LocationType)
address = graphene.NonNull(graphene.String())
city = graphene.NonNull(graphene.String())
class Query(graphene.ObjectType):
location = graphene.Field(Location, id=graphene.ID(description='location id'))
def resolve_location(self, args, info):
loc_id = args.get('id')
# error checking ommitted for brevity
return Location(**LOCATIONS[loc_id])
schema = graphene.Schema(name="Sample", query=Query)
EXPECTED_SCHEMA = """
type Location {
type: LocationType!
address: String!
city: String!
}
enum LocationType {
BRANCH
HQ
}
type Query {
location(id: ID): Location
}
""".lstrip()
QUERY = """
fragment locFields on Location {
type
address
city
}
query SomeQuery {
hq: location(id: 1) {
...locFields
}
branch: location(id: 2) {
...locFields
}
}
""".lstrip()
EXPECTED_RESPONSE = {
'hq': {
'type': 'HQ',
'address': '123 Main St',
'city': 'Metropolis'
},
'branch': {
'type': 'BRANCH',
'address': '55 Side St',
'city': 'Smallville'
},
}
def test_type():
assert str(schema) == EXPECTED_SCHEMA
result = schema.execute(QUERY)
assert not result.errors
assert result.data == EXPECTED_RESPONSE
With graphql-core
v0.4.9
, schemas now support subscriptions. I added support in epoxy
for them GraphQL-python-archive/graphql-epoxy@70e8b95 should be same level of difficulty in graphene
.
I see that the graphiql interface is able to introspect the schema, but I am not sure how you would export the schema form python. I see the introspect function on the Schema object, but that simply returns the objects from the type_map.
In the Star Wars relay example, it looks like you expect the resolve_ships
method of the Faction
class to return every ship. While this is feasible with a small dataset like that, it's not an assumption you can make. Does the resolve function get access to the first
and after
argument? Also, how do we supply pagination info?
Looking at the code, it looks like connection_from_list
deals with the resolved data as a complete list. Is there a way to get around this? If the resolve function only gets the arguments, but no way to supply pagination info, should we just return 1 more object than was requested, so that connection_from_list
knows there's more and can correctly create the pagination info?
It seems that there is support for the InputObject type defined in the spec, however, none of the examples cover its usage as an argument. Is this supported? If so, is it possible to add an example?
I have seen that the info
parameter in the resolve
function provides a info.field_asts
, which provides information about the selected fields, but when fragments are provided you get something like:
[Field(alias=None, name=Name(value=u'customer'), arguments=[], directives=[], selection_set=SelectionSet(selections=[Field(alias=None, name=Name(value=u'id'), arguments=[], directives=[], selection_set=None), FragmentSpread(name=Name(value=u'__RelayQueryFragment0wau8gf'), directives=[])]))]
which means for fragments we can't really figure out which fields are selected at runtime.
Our use-case for knowing the fields in the resolve functions is,that we only want to calculate the fields that are actually requested because some of the fields are expensive to calculate.
Edit: Or are the resolve
methods for specific fields meant to be used for that? E.g. resolve_full_name
on the Customer
node?
Also happy to provide an example of that would make it easier.
As the graphene syntax is now stable, the next focus should be internal refactor.
All class based types (ObjectType, Interface, Mutation, Union, Scalar) should have only the ClassType
in common (which assure the new type have a name and a description), but each type should customize the way or the attrs the options can have.
Right now a lot of logic is repeated for handling all this cases under one, divide this logic and use when necessary.
I've modified the toy example on the graphene playground to express the kind of thing I would like to be able to do. I want to have fields which depend on the output of other fields (on that object as well as potentially other connected objects). Right now I can make get the value for fields on a particular class by calling resolve_*() methods from within other resolve methods, but the result is not kept and so duplicate computation is done. Is there a different way to do this that doesn't result in duplicate computation? Is there a way to do this across object boundaries? For example defining a field which depends on summing the values of a field of an object in a has_many relation?
Inspired by other GraphQL implementations, next version should be able to:
graphene.{type}Field
to graphene.{type}
)..List
, .NonNull
to TypesIt has been in Django since 1.4 and can be converted to String. I can submit a PR but I don't know how you want it. Can it be done the same way as UUIDField?
Using Skulpt/Brython imitate the IDE client like used in https://facebook.github.io/relay/, but with Python schemas instead of JS ones.
Perhaps this is a known limitation, or perhaps I'm doing it wrong.
My I have the following (simplified) django models:
class Account(TimeStampedModel, models.Model):
name = models.CharField(max_length=100)
... more fields ...
class Transaction(TimeStampedModel, models.Model):
from_account = models.ForeignKey(Account, related_name='transactions_out')
... more fields ...
Desire: I want to traverse the Account.transactions_out
reverse relationship.
My Query & Nodes look like this:
class Query(ObjectType):
all_accounts = relay.ConnectionField(nodes.Account, description='All the accounts', args={
'name': graphene.String(),
})
all_transactions = relay.ConnectionField(nodes.Transaction, description='All the transactions')
account = relay.NodeField(nodes.Account)
transaction = relay.NodeField(nodes.Transaction)
@resolve_only_args
def resolve_all_accounts(self, **kwargs):
returns ...all accounts filtering for name...
@resolve_only_args
def resolve_all_transactions(self, **kwargs):
returns ...all transactions...
class Account(DjangoNode):
class Meta:
model = models.Account
# Transaction node omitted , but is exactly the same form as the Account node
When I run the following query:
query {
allAccounts(name: "adamcharnock") {
edges {
node {
id,
name,
transactionsOut {
edges {
node {
amount
}}}}}}}
The result comes back with "transactionsOut": null
, even though transactions do exist.
Solution: After some debugging I've found the following change will produce results:
class Account(DjangoNode):
class Meta:
model = models.Account
@classmethod
def resolve_transactionsOut(cls, instance, args, attrs):
return instance.instance.transactions_out.all()
Seems odd because: It seems like the Graphene's Django support does not:
transactionsOut
-> transactions_out
However – and as always – it is quite possible I'm doing this wrong. I'm also aware this project is a work in progress. Also, if GitHub issues are not the correct place for this I'm happy to move it elsewhere.
I am finding that once I have multiple relay.ConnectionField
fields attached to a Query, all requests will be validated against the second field
I have the following Query class:
class Query(ObjectType):
# Accounts have a 'name' field
all_accounts = relay.ConnectionField(nodes.Account, description='All the accounts')
# Transactions do not have a 'name' field
all_transactions = relay.ConnectionField(nodes.Transaction, description='All the transactions')
@resolve_only_args
def resolve_all_accounts(self, **kwargs):
return [nodes.Account(obj) for obj in models.Account.objects.all()]
@resolve_only_args
def resolve_all_transactions(self, **kwargs):
return [nodes.Transaction(obj) for obj in models.Transaction.objects.all()]
Upon running this query:
query {
allAccounts {
edges {
node {
name
}
}
}
}
I receive:
{
"errors": [
{
"locations": [
{
"column": 9,
"line": 5
}
],
"message": "Cannot query field \"name\" on \"Transaction\"."
}
]
}
Note that the above error seems to think I am querying Transaction
which, after tripple checking my code, I'm pretty sure I'm not. Also, if I switch the order of the fields on the Query class then the query will be successfully processed and data returned.
Caveat: I'm still pretty new to this, and the whole edges { nodes {
part of that query looks a little suspect to me. However, there are little to no examples I can find on anywhere online regarding how one should query for more than a single object. I'm almost entirely running off GraphiQL's autocomplete!
I am trying to introspect a schema that worked fine with graphene 0.4.0.1
but now breaks with 0.4.2
(unchanged), getting the following error:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "third_party/python/graphene/core/schema.py", line 126, in introspect
return self.execute(introspection_query).data
File "third_party/python/graphene/core/schema.py", line 117, in execute
self.schema,
File "third_party/python/graphene/core/schema.py", line 77, in schema
subscription=self.T(self.subscription))
File "third_party/python/graphene/core/schema.py", line 21, in __init__
super(GraphQLSchema, self).__init__(*args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'subscription'
I am not using subscriptions right now in any way.
I suspect this is related to b91ae4b.
I am currently on version 3712542.
The exception was:
Traceback (most recent call last):
File "/Users/adam/Envs/swiftwind/lib/python3.4/site-packages/django/core/handlers/base.py", line 132, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/adam/Envs/swiftwind/lib/python3.4/site-packages/django/contrib/auth/decorators.py", line 22, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/Users/adam/Envs/swiftwind/lib/python3.4/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/Users/adam/Envs/swiftwind/lib/python3.4/site-packages/django/views/generic/base.py", line 65, in view
self = cls(**initkwargs)
File "/Users/adam/Envs/swiftwind/src/graphene/graphene/contrib/django/views.py", line 10, in __init__
schema=schema.schema,
File "/Users/adam/Envs/swiftwind/src/graphene/graphene/core/schema.py", line 77, in schema
subscription=self.T(self.subscription))
File "/Users/adam/Envs/swiftwind/src/graphene/graphene/core/schema.py", line 21, in __init__
super(GraphQLSchema, self).__init__(*args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'subscription'
In my preferred SQLAlchemy setup, simply having the model class is not sufficient to load a model. In addition, you must have a separate session
object, which has a query method.
For example, the Django approach of :
ship = Ship.objects.get(id=1)
would instead work like this:
ship = session.query(Ship).filter_by(id=1).one()
I also require the ability to pass a notion of the current user into the query so that my resolvers can verify the user is authorized to access this data.
This means the 'session' and 'auth' objects must be provided to the schema.execute
method and get passed into any resolvers. I had planned to do this using the ExecutionContext. However, the get_node
classmethods don't get provided the ExecutionContext.
What do you suggest?
Hello, I am trying to experiment with a simple mutation, here is my schema:
import graphene
class Person(graphene.ObjectType):
name = graphene.String()
age = graphene.Float()
id = graphene.Int()
# Database of Person(s)
p1 = Person()
p2 = Person()
p1.id = 1
p1.age = 34
p1.name = 'Jack'
p2.id = 2
p2.age = 31
p2.name = 'Adam'
persons = [p1, p2]
class Query(graphene.ObjectType):
# Create a field on which we can query and the attributes
# we allow to query on
person = graphene.Field(Person, id=graphene.Int())
def resolve_person(self, args, info):
for p in persons:
if p.id == args.get('id'):
return p
class UpdatePerson(graphene.Mutation):
# Result field
# XXX: This doesn't work
# person = graphene.Field(Person)
# This does work
person = graphene.String()
# The input fields
class Input:
name = graphene.String()
id = graphene.Int()
@classmethod
def mutate(cls, instance, args, info):
name = args.get('name')
for p in persons:
if p.id == args.get('id'):
p.name = name
return UpdatePerson(person=p.name)
class UpdatePersonMutation(graphene.ObjectType):
updatePerson = graphene.Field(UpdatePerson)
schema = graphene.Schema(query=Query, mutation=UpdatePersonMutation)
In UpdatePerson
above, when I have the person field as person = graphene.Field(Person)
, ( and correspondingly return UpdatePerson(person=p)
), upon sending a mutation query, I get: [GraphQLError('Field "person" of type "Person" must have a sub selection.',)]
.
Any suggestions as to what I am doing wrong?
For some reason extend_fields
does not work on the playground but maybe the graphene version is outdated.
But how would it be possible to share graphene fields between the User
relay Node and the UpdateCustomerInput
to reduce duplication?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.