mirror of
https://github.com/graphql-python/graphene.git
synced 2025-09-21 11:22:33 +03:00
Merge branch 'master' into feature/add_subscription
This commit is contained in:
commit
50c2ac3354
|
@ -1,2 +1,2 @@
|
||||||
[settings]
|
[settings]
|
||||||
known_third_party = aniso8601,graphql,graphql_relay,promise,pytest,pytz,pyutils,setuptools,six,snapshottest,sphinx_graphene_theme
|
known_third_party = aniso8601,graphql,graphql_relay,promise,pytest,pytz,pyutils,setuptools,snapshottest,sphinx_graphene_theme
|
||||||
|
|
|
@ -4,6 +4,7 @@ dist: xenial
|
||||||
python:
|
python:
|
||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.7"
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install tox tox-travis
|
- pip install tox tox-travis
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -5,10 +5,10 @@ help:
|
||||||
|
|
||||||
.PHONY: install-dev ## Install development dependencies
|
.PHONY: install-dev ## Install development dependencies
|
||||||
install-dev:
|
install-dev:
|
||||||
pip install -e ".[test]"
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
py.test graphene
|
py.test graphene examples tests_asyncio
|
||||||
|
|
||||||
.PHONY: docs ## Generate docs
|
.PHONY: docs ## Generate docs
|
||||||
docs: install-dev
|
docs: install-dev
|
||||||
|
@ -17,3 +17,11 @@ docs: install-dev
|
||||||
.PHONY: docs-live ## Generate docs with live reloading
|
.PHONY: docs-live ## Generate docs with live reloading
|
||||||
docs-live: install-dev
|
docs-live: install-dev
|
||||||
cd docs && make install && make livehtml
|
cd docs && make install && make livehtml
|
||||||
|
|
||||||
|
.PHONY: format
|
||||||
|
format:
|
||||||
|
black graphene examples setup.py tests_asyncio
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
flake8 graphene examples setup.py tests_asyncio
|
||||||
|
|
15
README.md
15
README.md
|
@ -1,12 +1,18 @@
|
||||||
|
#  [Graphene](http://graphene-python.org) [](https://travis-ci.org/graphql-python/graphene) [](https://badge.fury.io/py/graphene) [](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
||||||
|
|
||||||
|
[💬 Join the community on Slack](https://join.slack.com/t/graphenetools/shared_invite/enQtOTE2MDQ1NTg4MDM1LTA4Nzk0MGU0NGEwNzUxZGNjNDQ4ZjAwNDJjMjY0OGE1ZDgxZTg4YjM2ZTc4MjE2ZTAzZjE2ZThhZTQzZTkyMmM)
|
||||||
|
|
||||||
**We are looking for contributors**! Please check the [ROADMAP](https://github.com/graphql-python/graphene/blob/master/ROADMAP.md) to see how you can help ❤️
|
**We are looking for contributors**! Please check the [ROADMAP](https://github.com/graphql-python/graphene/blob/master/ROADMAP.md) to see how you can help ❤️
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#  [Graphene](http://graphene-python.org) [](https://travis-ci.org/graphql-python/graphene) [](https://badge.fury.io/py/graphene) [](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
**The below readme is the documentation for the `dev` (prerelease) version of Graphene. To view the documentation for the latest stable Graphene version go to the [v2 docs](https://docs.graphene-python.org/en/stable/)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
[Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily.
|
[Graphene](http://graphene-python.org) is an opinionated Python library for building GraphQL schemas/types fast and easily.
|
||||||
|
|
||||||
- **Easy to use:** Graphene helps you use GraphQL in Python without effort.
|
- **Easy to use:** Graphene helps you use GraphQL in Python without effort.
|
||||||
- **Relay:** Graphene has builtin support for Relay.
|
- **Relay:** Graphene has builtin support for Relay.
|
||||||
|
@ -23,7 +29,6 @@ Graphene has multiple integrations with different frameworks:
|
||||||
| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
|
| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
|
||||||
| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
|
| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
|
||||||
| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) |
|
| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) |
|
||||||
| Peewee | _In progress_ ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) |
|
|
||||||
|
|
||||||
Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql).
|
Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql).
|
||||||
|
|
||||||
|
@ -35,10 +40,6 @@ For instaling graphene, just run this command in your shell
|
||||||
pip install "graphene>=2.0"
|
pip install "graphene>=2.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2.0 Upgrade Guide
|
|
||||||
|
|
||||||
Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Here is one example for you to get started:
|
Here is one example for you to get started:
|
||||||
|
|
|
@ -4,7 +4,7 @@ Executing a query
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|
||||||
For executing a query a schema, you can directly call the ``execute`` method on it.
|
For executing a query against a schema, you can directly call the ``execute`` method on it.
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
8
docs/execution/fileuploading.rst
Normal file
8
docs/execution/fileuploading.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
File uploading
|
||||||
|
==============
|
||||||
|
|
||||||
|
File uploading is not part of the official GraphQL spec yet and is not natively
|
||||||
|
implemented in Graphene.
|
||||||
|
|
||||||
|
If your server needs to support file uploading then you can use the libary: `graphene-file-upload <https://github.com/lmcgartland/graphene-file-upload>`_ which enhances Graphene to add file
|
||||||
|
uploads and conforms to the unoffical GraphQL `multipart request spec <https://github.com/jaydenseric/graphql-multipart-request-spec>`_.
|
|
@ -8,3 +8,4 @@ Execution
|
||||||
execute
|
execute
|
||||||
middleware
|
middleware
|
||||||
dataloader
|
dataloader
|
||||||
|
fileuploading
|
||||||
|
|
|
@ -29,7 +29,7 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'`
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class AuthorizationMiddleware(object):
|
class AuthorizationMiddleware(object):
|
||||||
def resolve(next, root, info, **args):
|
def resolve(self, next, root, info, **args):
|
||||||
if info.field_name == 'user':
|
if info.field_name == 'user':
|
||||||
return None
|
return None
|
||||||
return next(root, info, **args)
|
return next(root, info, **args)
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
Graphene
|
Graphene
|
||||||
========
|
========
|
||||||
|
|
||||||
|
------------
|
||||||
|
|
||||||
|
The documentation below is for the ``dev`` (prerelease) version of Graphene. To view the documentation for the latest stable Graphene version go to the `v2 docs <https://docs.graphene-python.org/en/stable/>`_.
|
||||||
|
|
||||||
|
------------
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
|
@ -30,7 +30,7 @@ Compare Graphene's *code-first* approach to building a GraphQL API with *schema-
|
||||||
|
|
||||||
.. _Ariadne: https://ariadne.readthedocs.io
|
.. _Ariadne: https://ariadne.readthedocs.io
|
||||||
|
|
||||||
Graphene is fully featured with integrations for the most popular web frameworks and ORMs. Graphene produces schemas tha are fully compliant with the GraphQL spec and provides tools and patterns for building a Relay-Compliant API as well.
|
Graphene is fully featured with integrations for the most popular web frameworks and ORMs. Graphene produces schemas that are fully compliant with the GraphQL spec and provides tools and patterns for building a Relay-Compliant API as well.
|
||||||
|
|
||||||
An example in Graphene
|
An example in Graphene
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -127,7 +127,7 @@ Then we can start querying our **Schema** by passing a GraphQL query string to `
|
||||||
query_string = '{ hello }'
|
query_string = '{ hello }'
|
||||||
result = schema.execute(query_string)
|
result = schema.execute(query_string)
|
||||||
print(result.data['hello'])
|
print(result.data['hello'])
|
||||||
# "Hello stranger"
|
# "Hello stranger!"
|
||||||
|
|
||||||
# or passing the argument in the query
|
# or passing the argument in the query
|
||||||
query_with_argument = '{ hello(name: "GraphQL") }'
|
query_with_argument = '{ hello(name: "GraphQL") }'
|
||||||
|
|
|
@ -101,7 +101,7 @@ When we execute a query against that schema.
|
||||||
query_string = "{ me { fullName } }"
|
query_string = "{ me { fullName } }"
|
||||||
result = schema.execute(query_string)
|
result = schema.execute(query_string)
|
||||||
|
|
||||||
assert result["data"]["me"] == {"fullName": "Luke Skywalker")
|
assert result.data["me"] == {"fullName": "Luke Skywalker")
|
||||||
|
|
||||||
Then we go through the following steps to resolve this query:
|
Then we go through the following steps to resolve this query:
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ If the :ref:`ResolverParamParent` is a dictionary, the resolver will look for a
|
||||||
|
|
||||||
from graphene import ObjectType, String, Field, Schema
|
from graphene import ObjectType, String, Field, Schema
|
||||||
|
|
||||||
PersonValueObject = namedtuple('Person', 'first_name', 'last_name')
|
PersonValueObject = namedtuple("Person", ["first_name", "last_name"])
|
||||||
|
|
||||||
class Person(ObjectType):
|
class Person(ObjectType):
|
||||||
first_name = String()
|
first_name = String()
|
||||||
|
@ -224,7 +224,7 @@ If the :ref:`ResolverParamParent` is a dictionary, the resolver will look for a
|
||||||
|
|
||||||
def resolve_me(parent, info):
|
def resolve_me(parent, info):
|
||||||
# always pass an object for `me` field
|
# always pass an object for `me` field
|
||||||
return PersonValueObject(first_name='Luke', last_name='Skywalker')
|
return PersonValueObject(first_name="Luke", last_name="Skywalker")
|
||||||
|
|
||||||
def resolve_my_best_friend(parent, info):
|
def resolve_my_best_friend(parent, info):
|
||||||
# always pass a dictionary for `my_best_fiend_field`
|
# always pass a dictionary for `my_best_fiend_field`
|
||||||
|
@ -238,10 +238,10 @@ If the :ref:`ResolverParamParent` is a dictionary, the resolver will look for a
|
||||||
}
|
}
|
||||||
''')
|
''')
|
||||||
# With default resolvers we can resolve attributes from an object..
|
# With default resolvers we can resolve attributes from an object..
|
||||||
assert result['data']['me'] == {"firstName": "Luke", "lastName": "Skywalker"}
|
assert result.data["me"] == {"firstName": "Luke", "lastName": "Skywalker"}
|
||||||
|
|
||||||
# With default resolvers, we can also resolve keys from a dictionary..
|
# With default resolvers, we can also resolve keys from a dictionary..
|
||||||
assert result['data']['my_best_friend'] == {"firstName": "R2", "lastName": "D2"}
|
assert result.data["myBestFriend"] == {"firstName": "R2", "lastName": "D2"}
|
||||||
|
|
||||||
Advanced
|
Advanced
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
@ -280,7 +280,7 @@ An error will be thrown:
|
||||||
|
|
||||||
TypeError: resolve_hello() missing 1 required positional argument: 'name'
|
TypeError: resolve_hello() missing 1 required positional argument: 'name'
|
||||||
|
|
||||||
You can fix this error in serveral ways. Either by combining all keyword arguments
|
You can fix this error in several ways. Either by combining all keyword arguments
|
||||||
into a dict:
|
into a dict:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from snapshottest import Snapshot
|
from snapshottest import Snapshot
|
||||||
|
|
||||||
|
|
||||||
snapshots = Snapshot()
|
snapshots = Snapshot()
|
||||||
|
|
||||||
snapshots["test_correctly_fetches_id_name_rebels 1"] = {
|
snapshots["test_correctly_fetches_id_name_rebels 1"] = {
|
||||||
|
@ -30,7 +31,16 @@ snapshots["test_correctly_refetches_xwing 1"] = {
|
||||||
|
|
||||||
snapshots[
|
snapshots[
|
||||||
"test_str_schema 1"
|
"test_str_schema 1"
|
||||||
] = '''"""A faction in the Star Wars saga"""
|
] = '''type Query {
|
||||||
|
rebels: Faction
|
||||||
|
empire: Faction
|
||||||
|
node(
|
||||||
|
"""The ID of the object"""
|
||||||
|
id: ID!
|
||||||
|
): Node
|
||||||
|
}
|
||||||
|
|
||||||
|
"""A faction in the Star Wars saga"""
|
||||||
type Faction implements Node {
|
type Faction implements Node {
|
||||||
"""The ID of the object"""
|
"""The ID of the object"""
|
||||||
id: ID!
|
id: ID!
|
||||||
|
@ -42,28 +52,20 @@ type Faction implements Node {
|
||||||
ships(before: String = null, after: String = null, first: Int = null, last: Int = null): ShipConnection
|
ships(before: String = null, after: String = null, first: Int = null, last: Int = null): ShipConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
input IntroduceShipInput {
|
|
||||||
shipName: String!
|
|
||||||
factionId: String!
|
|
||||||
clientMutationId: String = null
|
|
||||||
}
|
|
||||||
|
|
||||||
type IntroduceShipPayload {
|
|
||||||
ship: Ship
|
|
||||||
faction: Faction
|
|
||||||
clientMutationId: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
introduceShip(input: IntroduceShipInput!): IntroduceShipPayload
|
|
||||||
}
|
|
||||||
|
|
||||||
"""An object with an ID"""
|
"""An object with an ID"""
|
||||||
interface Node {
|
interface Node {
|
||||||
"""The ID of the object"""
|
"""The ID of the object"""
|
||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShipConnection {
|
||||||
|
"""Pagination data for this connection."""
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
|
||||||
|
"""Contains the nodes in this connection."""
|
||||||
|
edges: [ShipEdge]!
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The Relay compliant `PageInfo` type, containing data necessary to paginate this connection.
|
The Relay compliant `PageInfo` type, containing data necessary to paginate this connection.
|
||||||
"""
|
"""
|
||||||
|
@ -81,12 +83,13 @@ type PageInfo {
|
||||||
endCursor: String
|
endCursor: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
"""A Relay edge containing a `Ship` and its cursor."""
|
||||||
rebels: Faction
|
type ShipEdge {
|
||||||
empire: Faction
|
"""The item at the end of the edge"""
|
||||||
|
node: Ship
|
||||||
|
|
||||||
"""The ID of the object"""
|
"""A cursor for use in pagination"""
|
||||||
node(id: ID!): Node
|
cursor: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
"""A ship in the Star Wars saga"""
|
"""A ship in the Star Wars saga"""
|
||||||
|
@ -98,20 +101,19 @@ type Ship implements Node {
|
||||||
name: String
|
name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShipConnection {
|
type Mutation {
|
||||||
"""Pagination data for this connection."""
|
introduceShip(input: IntroduceShipInput!): IntroduceShipPayload
|
||||||
pageInfo: PageInfo!
|
|
||||||
|
|
||||||
"""Contains the nodes in this connection."""
|
|
||||||
edges: [ShipEdge]!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""A Relay edge containing a `Ship` and its cursor."""
|
type IntroduceShipPayload {
|
||||||
type ShipEdge {
|
ship: Ship
|
||||||
"""The item at the end of the edge"""
|
faction: Faction
|
||||||
node: Ship
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
"""A cursor for use in pagination"""
|
input IntroduceShipInput {
|
||||||
cursor: String!
|
shipName: String!
|
||||||
|
factionId: String!
|
||||||
|
clientMutationId: String
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -42,7 +42,7 @@ from .utils.resolve_only_args import resolve_only_args
|
||||||
from .utils.module_loading import lazy_import
|
from .utils.module_loading import lazy_import
|
||||||
|
|
||||||
|
|
||||||
VERSION = (3, 0, 0, "alpha", 0)
|
VERSION = (3, 0, 0, "beta", 0)
|
||||||
|
|
||||||
|
|
||||||
__version__ = get_version(VERSION)
|
__version__ = get_version(VERSION)
|
||||||
|
|
|
@ -90,9 +90,24 @@ class Node(AbstractNode):
|
||||||
def get_node_from_global_id(cls, info, global_id, only_type=None):
|
def get_node_from_global_id(cls, info, global_id, only_type=None):
|
||||||
try:
|
try:
|
||||||
_type, _id = cls.from_global_id(global_id)
|
_type, _id = cls.from_global_id(global_id)
|
||||||
graphene_type = info.schema.get_type(_type).graphene_type
|
except Exception as e:
|
||||||
except Exception:
|
raise Exception(
|
||||||
return None
|
(
|
||||||
|
'Unable to parse global ID "{global_id}". '
|
||||||
|
'Make sure it is a base64 encoded string in the format: "TypeName:id". '
|
||||||
|
"Exception message: {exception}".format(
|
||||||
|
global_id=global_id, exception=str(e)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
graphene_type = info.schema.get_type(_type)
|
||||||
|
if graphene_type is None:
|
||||||
|
raise Exception(
|
||||||
|
'Relay Node "{_type}" not found in schema'.format(_type=_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
graphene_type = graphene_type.graphene_type
|
||||||
|
|
||||||
if only_type:
|
if only_type:
|
||||||
assert graphene_type == only_type, ("Must receive a {} id.").format(
|
assert graphene_type == only_type, ("Must receive a {} id.").format(
|
||||||
|
@ -101,7 +116,11 @@ class Node(AbstractNode):
|
||||||
|
|
||||||
# We make sure the ObjectType implements the "Node" interface
|
# We make sure the ObjectType implements the "Node" interface
|
||||||
if cls not in graphene_type._meta.interfaces:
|
if cls not in graphene_type._meta.interfaces:
|
||||||
return None
|
raise Exception(
|
||||||
|
'ObjectType "{_type}" does not implement the "{cls}" interface.'.format(
|
||||||
|
_type=_type, cls=cls
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
get_node = getattr(graphene_type, "get_node", None)
|
get_node = getattr(graphene_type, "get_node", None)
|
||||||
if get_node:
|
if get_node:
|
||||||
|
|
|
@ -80,11 +80,11 @@ class OtherMutation(ClientIDMutation):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate_and_get_payload(
|
def mutate_and_get_payload(
|
||||||
self, info, shared, additional_field, client_mutation_id=None
|
self, info, shared="", additional_field="", client_mutation_id=None
|
||||||
):
|
):
|
||||||
edge_type = MyEdge
|
edge_type = MyEdge
|
||||||
return OtherMutation(
|
return OtherMutation(
|
||||||
name=(shared or "") + (additional_field or ""),
|
name=shared + additional_field,
|
||||||
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
|
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import re
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from graphql.pyutils import dedent
|
from graphql.pyutils import dedent
|
||||||
|
@ -83,6 +84,20 @@ def test_node_requesting_non_node():
|
||||||
executed = schema.execute(
|
executed = schema.execute(
|
||||||
'{ node(id:"%s") { __typename } } ' % Node.to_global_id("RootQuery", 1)
|
'{ node(id:"%s") { __typename } } ' % Node.to_global_id("RootQuery", 1)
|
||||||
)
|
)
|
||||||
|
assert executed.errors
|
||||||
|
assert re.match(
|
||||||
|
r"ObjectType .* does not implement the .* interface.",
|
||||||
|
executed.errors[0].message,
|
||||||
|
)
|
||||||
|
assert executed.data == {"node": None}
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_requesting_unknown_type():
|
||||||
|
executed = schema.execute(
|
||||||
|
'{ node(id:"%s") { __typename } } ' % Node.to_global_id("UnknownType", 1)
|
||||||
|
)
|
||||||
|
assert executed.errors
|
||||||
|
assert re.match(r"Relay Node .* not found in schema", executed.errors[0].message)
|
||||||
assert executed.data == {"node": None}
|
assert executed.data == {"node": None}
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,7 +105,8 @@ def test_node_query_incorrect_id():
|
||||||
executed = schema.execute(
|
executed = schema.execute(
|
||||||
'{ node(id:"%s") { ... on MyNode { name } } }' % "something:2"
|
'{ node(id:"%s") { ... on MyNode { name } } }' % "something:2"
|
||||||
)
|
)
|
||||||
assert not executed.errors
|
assert executed.errors
|
||||||
|
assert re.match(r"Unable to parse global ID .*", executed.errors[0].message)
|
||||||
assert executed.data == {"node": None}
|
assert executed.data == {"node": None}
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,6 +183,12 @@ def test_str_schema():
|
||||||
name: String
|
name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""An object with an ID"""
|
||||||
|
interface Node {
|
||||||
|
"""The ID of the object"""
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
type MyOtherNode implements Node {
|
type MyOtherNode implements Node {
|
||||||
"""The ID of the object"""
|
"""The ID of the object"""
|
||||||
id: ID!
|
id: ID!
|
||||||
|
@ -175,23 +197,20 @@ def test_str_schema():
|
||||||
extraField: String
|
extraField: String
|
||||||
}
|
}
|
||||||
|
|
||||||
"""An object with an ID"""
|
|
||||||
interface Node {
|
|
||||||
"""The ID of the object"""
|
|
||||||
id: ID!
|
|
||||||
}
|
|
||||||
|
|
||||||
type RootQuery {
|
type RootQuery {
|
||||||
first: String
|
first: String
|
||||||
|
node(
|
||||||
"""The ID of the object"""
|
"""The ID of the object"""
|
||||||
node(id: ID!): Node
|
id: ID!
|
||||||
|
): Node
|
||||||
"""The ID of the object"""
|
onlyNode(
|
||||||
onlyNode(id: ID!): MyNode
|
"""The ID of the object"""
|
||||||
|
id: ID!
|
||||||
"""The ID of the object"""
|
): MyNode
|
||||||
onlyNodeLazy(id: ID!): MyNode
|
onlyNodeLazy(
|
||||||
|
"""The ID of the object"""
|
||||||
|
id: ID!
|
||||||
|
): MyNode
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,9 +59,12 @@ def test_str_schema_correct():
|
||||||
query: RootQuery
|
query: RootQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BasePhoto {
|
type User implements Node {
|
||||||
"""The width of the photo in pixels"""
|
"""The ID of the object"""
|
||||||
width: Int
|
id: ID!
|
||||||
|
|
||||||
|
"""The full name of the user"""
|
||||||
|
name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Node {
|
interface Node {
|
||||||
|
@ -77,17 +80,16 @@ def test_str_schema_correct():
|
||||||
width: Int
|
width: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type RootQuery {
|
interface BasePhoto {
|
||||||
"""The ID of the object"""
|
"""The width of the photo in pixels"""
|
||||||
node(id: ID!): Node
|
width: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type User implements Node {
|
type RootQuery {
|
||||||
"""The ID of the object"""
|
node(
|
||||||
id: ID!
|
"""The ID of the object"""
|
||||||
|
id: ID!
|
||||||
"""The full name of the user"""
|
): Node
|
||||||
name: String
|
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,7 +27,7 @@ def test_issue():
|
||||||
graphene.Schema(query=Query)
|
graphene.Schema(query=Query)
|
||||||
|
|
||||||
assert str(exc_info.value) == (
|
assert str(exc_info.value) == (
|
||||||
"Query fields cannot be resolved:"
|
"Query fields cannot be resolved."
|
||||||
" IterableConnectionField type has to be a subclass of Connection."
|
" IterableConnectionField type has to be a subclass of Connection."
|
||||||
' Received "MyUnion".'
|
' Received "MyUnion".'
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,8 +3,8 @@ from __future__ import absolute_import
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from aniso8601 import parse_date, parse_datetime, parse_time
|
from aniso8601 import parse_date, parse_datetime, parse_time
|
||||||
from graphql.error import INVALID
|
from graphql.error import GraphQLError
|
||||||
from graphql.language import StringValueNode
|
from graphql.language import StringValueNode, print_ast
|
||||||
|
|
||||||
from .scalars import Scalar
|
from .scalars import Scalar
|
||||||
|
|
||||||
|
@ -20,25 +20,30 @@ class Date(Scalar):
|
||||||
def serialize(date):
|
def serialize(date):
|
||||||
if isinstance(date, datetime.datetime):
|
if isinstance(date, datetime.datetime):
|
||||||
date = date.date()
|
date = date.date()
|
||||||
assert isinstance(
|
if not isinstance(date, datetime.date):
|
||||||
date, datetime.date
|
raise GraphQLError("Date cannot represent value: {}".format(repr(date)))
|
||||||
), 'Received not compatible date "{}"'.format(repr(date))
|
|
||||||
return date.isoformat()
|
return date.isoformat()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_literal(cls, node):
|
def parse_literal(cls, node):
|
||||||
if isinstance(node, StringValueNode):
|
if not isinstance(node, StringValueNode):
|
||||||
return cls.parse_value(node.value)
|
raise GraphQLError(
|
||||||
|
"Date cannot represent non-string value: {}".format(print_ast(node))
|
||||||
|
)
|
||||||
|
return cls.parse_value(node.value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_value(value):
|
def parse_value(value):
|
||||||
|
if isinstance(value, datetime.date):
|
||||||
|
return value
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise GraphQLError(
|
||||||
|
"Date cannot represent non-string value: {}".format(repr(value))
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
if isinstance(value, datetime.date):
|
return parse_date(value)
|
||||||
return value
|
|
||||||
elif isinstance(value, str):
|
|
||||||
return parse_date(value)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return INVALID
|
raise GraphQLError("Date cannot represent value: {}".format(repr(value)))
|
||||||
|
|
||||||
|
|
||||||
class DateTime(Scalar):
|
class DateTime(Scalar):
|
||||||
|
@ -50,25 +55,32 @@ class DateTime(Scalar):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def serialize(dt):
|
def serialize(dt):
|
||||||
assert isinstance(
|
if not isinstance(dt, (datetime.datetime, datetime.date)):
|
||||||
dt, (datetime.datetime, datetime.date)
|
raise GraphQLError("DateTime cannot represent value: {}".format(repr(dt)))
|
||||||
), 'Received not compatible datetime "{}"'.format(repr(dt))
|
|
||||||
return dt.isoformat()
|
return dt.isoformat()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_literal(cls, node):
|
def parse_literal(cls, node):
|
||||||
if isinstance(node, StringValueNode):
|
if not isinstance(node, StringValueNode):
|
||||||
return cls.parse_value(node.value)
|
raise GraphQLError(
|
||||||
|
"DateTime cannot represent non-string value: {}".format(print_ast(node))
|
||||||
|
)
|
||||||
|
return cls.parse_value(node.value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_value(value):
|
def parse_value(value):
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
return value
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise GraphQLError(
|
||||||
|
"DateTime cannot represent non-string value: {}".format(repr(value))
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
if isinstance(value, datetime.datetime):
|
return parse_datetime(value)
|
||||||
return value
|
|
||||||
elif isinstance(value, str):
|
|
||||||
return parse_datetime(value)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return INVALID
|
raise GraphQLError(
|
||||||
|
"DateTime cannot represent value: {}".format(repr(value))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Time(Scalar):
|
class Time(Scalar):
|
||||||
|
@ -80,22 +92,27 @@ class Time(Scalar):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def serialize(time):
|
def serialize(time):
|
||||||
assert isinstance(
|
if not isinstance(time, datetime.time):
|
||||||
time, datetime.time
|
raise GraphQLError("Time cannot represent value: {}".format(repr(time)))
|
||||||
), 'Received not compatible time "{}"'.format(repr(time))
|
|
||||||
return time.isoformat()
|
return time.isoformat()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_literal(cls, node):
|
def parse_literal(cls, node):
|
||||||
if isinstance(node, StringValueNode):
|
if not isinstance(node, StringValueNode):
|
||||||
return cls.parse_value(node.value)
|
raise GraphQLError(
|
||||||
|
"Time cannot represent non-string value: {}".format(print_ast(node))
|
||||||
|
)
|
||||||
|
return cls.parse_value(node.value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_value(cls, value):
|
def parse_value(cls, value):
|
||||||
|
if isinstance(value, datetime.time):
|
||||||
|
return value
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise GraphQLError(
|
||||||
|
"Time cannot represent non-string value: {}".format(repr(value))
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
if isinstance(value, datetime.time):
|
return parse_time(value)
|
||||||
return value
|
|
||||||
elif isinstance(value, str):
|
|
||||||
return parse_time(value)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return INVALID
|
raise GraphQLError("Time cannot represent value: {}".format(repr(value)))
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from graphql import Undefined
|
||||||
|
|
||||||
from .mountedtype import MountedType
|
from .mountedtype import MountedType
|
||||||
from .structures import NonNull
|
from .structures import NonNull
|
||||||
from .utils import get_type
|
from .utils import get_type
|
||||||
|
@ -48,7 +50,7 @@ class InputField(MountedType):
|
||||||
self,
|
self,
|
||||||
type,
|
type,
|
||||||
name=None,
|
name=None,
|
||||||
default_value=None,
|
default_value=Undefined,
|
||||||
deprecation_reason=None,
|
deprecation_reason=None,
|
||||||
description=None,
|
description=None,
|
||||||
required=False,
|
required=False,
|
||||||
|
|
|
@ -68,4 +68,4 @@ class Interface(BaseType):
|
||||||
return type(instance)
|
return type(instance)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
raise Exception("An Interface cannot be intitialized")
|
raise Exception("An Interface cannot be initialized")
|
||||||
|
|
|
@ -7,7 +7,6 @@ from graphql import (
|
||||||
graphql,
|
graphql,
|
||||||
graphql_sync,
|
graphql_sync,
|
||||||
introspection_types,
|
introspection_types,
|
||||||
is_type,
|
|
||||||
parse,
|
parse,
|
||||||
print_schema,
|
print_schema,
|
||||||
subscribe,
|
subscribe,
|
||||||
|
@ -24,7 +23,7 @@ from graphql import (
|
||||||
GraphQLObjectType,
|
GraphQLObjectType,
|
||||||
GraphQLSchema,
|
GraphQLSchema,
|
||||||
GraphQLString,
|
GraphQLString,
|
||||||
INVALID,
|
Undefined,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..utils.str_converters import to_camel_case
|
from ..utils.str_converters import to_camel_case
|
||||||
|
@ -73,118 +72,74 @@ def is_graphene_type(type_):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def resolve_type(resolve_type_func, map_, type_name, root, info, _type):
|
|
||||||
type_ = resolve_type_func(root, info)
|
|
||||||
|
|
||||||
if not type_:
|
|
||||||
return_type = map_[type_name]
|
|
||||||
return default_type_resolver(root, info, return_type)
|
|
||||||
|
|
||||||
if inspect.isclass(type_) and issubclass(type_, ObjectType):
|
|
||||||
graphql_type = map_.get(type_._meta.name)
|
|
||||||
assert graphql_type, "Can't find type {} in schema".format(type_._meta.name)
|
|
||||||
assert graphql_type.graphene_type == type_, (
|
|
||||||
"The type {} does not match with the associated graphene type {}."
|
|
||||||
).format(type_, graphql_type.graphene_type)
|
|
||||||
return graphql_type
|
|
||||||
|
|
||||||
return type_
|
|
||||||
|
|
||||||
|
|
||||||
def is_type_of_from_possible_types(possible_types, root, _info):
|
def is_type_of_from_possible_types(possible_types, root, _info):
|
||||||
return isinstance(root, possible_types)
|
return isinstance(root, possible_types)
|
||||||
|
|
||||||
|
|
||||||
class GrapheneGraphQLSchema(GraphQLSchema):
|
class TypeMap(dict):
|
||||||
"""A GraphQLSchema that can deal with Graphene types as well."""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
query=None,
|
query=None,
|
||||||
mutation=None,
|
mutation=None,
|
||||||
subscription=None,
|
subscription=None,
|
||||||
types=None,
|
types=None,
|
||||||
directives=None,
|
|
||||||
auto_camelcase=True,
|
auto_camelcase=True,
|
||||||
):
|
):
|
||||||
assert_valid_root_type(query)
|
assert_valid_root_type(query)
|
||||||
assert_valid_root_type(mutation)
|
assert_valid_root_type(mutation)
|
||||||
assert_valid_root_type(subscription)
|
assert_valid_root_type(subscription)
|
||||||
|
if types is None:
|
||||||
|
types = []
|
||||||
|
for type_ in types:
|
||||||
|
assert is_graphene_type(type_)
|
||||||
|
|
||||||
self.auto_camelcase = auto_camelcase
|
self.auto_camelcase = auto_camelcase
|
||||||
super().__init__(query, mutation, subscription, types, directives)
|
|
||||||
|
|
||||||
if query:
|
create_graphql_type = self.add_type
|
||||||
self.query_type = self.get_type(
|
|
||||||
query.name if isinstance(query, GraphQLObjectType) else query._meta.name
|
|
||||||
)
|
|
||||||
if mutation:
|
|
||||||
self.mutation_type = self.get_type(
|
|
||||||
mutation.name
|
|
||||||
if isinstance(mutation, GraphQLObjectType)
|
|
||||||
else mutation._meta.name
|
|
||||||
)
|
|
||||||
if subscription:
|
|
||||||
self.subscription_type = self.get_type(
|
|
||||||
subscription.name
|
|
||||||
if isinstance(subscription, GraphQLObjectType)
|
|
||||||
else subscription._meta.name
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_graphql_type(self, _type):
|
self.query = create_graphql_type(query) if query else None
|
||||||
if not _type:
|
self.mutation = create_graphql_type(mutation) if mutation else None
|
||||||
return _type
|
self.subscription = create_graphql_type(subscription) if subscription else None
|
||||||
if is_type(_type):
|
|
||||||
return _type
|
self.types = [create_graphql_type(graphene_type) for graphene_type in types]
|
||||||
if is_graphene_type(_type):
|
|
||||||
graphql_type = self.get_type(_type._meta.name)
|
def add_type(self, graphene_type):
|
||||||
assert graphql_type, "Type {} not found in this schema.".format(
|
if inspect.isfunction(graphene_type):
|
||||||
_type._meta.name
|
graphene_type = graphene_type()
|
||||||
|
if isinstance(graphene_type, List):
|
||||||
|
return GraphQLList(self.add_type(graphene_type.of_type))
|
||||||
|
if isinstance(graphene_type, NonNull):
|
||||||
|
return GraphQLNonNull(self.add_type(graphene_type.of_type))
|
||||||
|
try:
|
||||||
|
name = graphene_type._meta.name
|
||||||
|
except AttributeError:
|
||||||
|
raise TypeError(
|
||||||
|
"Expected Graphene type, but received: {}.".format(graphene_type)
|
||||||
)
|
)
|
||||||
assert graphql_type.graphene_type == _type
|
graphql_type = self.get(name)
|
||||||
|
if graphql_type:
|
||||||
return graphql_type
|
return graphql_type
|
||||||
raise Exception("{} is not a valid GraphQL type.".format(_type))
|
if issubclass(graphene_type, ObjectType):
|
||||||
|
graphql_type = self.create_objecttype(graphene_type)
|
||||||
# noinspection PyMethodOverriding
|
elif issubclass(graphene_type, InputObjectType):
|
||||||
def type_map_reducer(self, map_, type_):
|
graphql_type = self.create_inputobjecttype(graphene_type)
|
||||||
if not type_:
|
elif issubclass(graphene_type, Interface):
|
||||||
return map_
|
graphql_type = self.create_interface(graphene_type)
|
||||||
if inspect.isfunction(type_):
|
elif issubclass(graphene_type, Scalar):
|
||||||
type_ = type_()
|
graphql_type = self.create_scalar(graphene_type)
|
||||||
if is_graphene_type(type_):
|
elif issubclass(graphene_type, Enum):
|
||||||
return self.graphene_reducer(map_, type_)
|
graphql_type = self.create_enum(graphene_type)
|
||||||
return super().type_map_reducer(map_, type_)
|
elif issubclass(graphene_type, Union):
|
||||||
|
graphql_type = self.construct_union(graphene_type)
|
||||||
def graphene_reducer(self, map_, type_):
|
|
||||||
if isinstance(type_, (List, NonNull)):
|
|
||||||
return self.type_map_reducer(map_, type_.of_type)
|
|
||||||
if type_._meta.name in map_:
|
|
||||||
_type = map_[type_._meta.name]
|
|
||||||
if isinstance(_type, GrapheneGraphQLType):
|
|
||||||
assert _type.graphene_type == type_, (
|
|
||||||
"Found different types with the same name in the schema: {}, {}."
|
|
||||||
).format(_type.graphene_type, type_)
|
|
||||||
return map_
|
|
||||||
|
|
||||||
if issubclass(type_, ObjectType):
|
|
||||||
internal_type = self.construct_objecttype(map_, type_)
|
|
||||||
elif issubclass(type_, InputObjectType):
|
|
||||||
internal_type = self.construct_inputobjecttype(map_, type_)
|
|
||||||
elif issubclass(type_, Interface):
|
|
||||||
internal_type = self.construct_interface(map_, type_)
|
|
||||||
elif issubclass(type_, Scalar):
|
|
||||||
internal_type = self.construct_scalar(type_)
|
|
||||||
elif issubclass(type_, Enum):
|
|
||||||
internal_type = self.construct_enum(type_)
|
|
||||||
elif issubclass(type_, Union):
|
|
||||||
internal_type = self.construct_union(map_, type_)
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Expected Graphene type, but received: {}.".format(type_))
|
raise TypeError(
|
||||||
|
"Expected Graphene type, but received: {}.".format(graphene_type)
|
||||||
return super().type_map_reducer(map_, internal_type)
|
)
|
||||||
|
self[name] = graphql_type
|
||||||
|
return graphql_type
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def construct_scalar(type_):
|
def create_scalar(graphene_type):
|
||||||
# We have a mapping to the original GraphQL types
|
# We have a mapping to the original GraphQL types
|
||||||
# so there are no collisions.
|
# so there are no collisions.
|
||||||
_scalars = {
|
_scalars = {
|
||||||
|
@ -194,29 +149,31 @@ class GrapheneGraphQLSchema(GraphQLSchema):
|
||||||
Boolean: GraphQLBoolean,
|
Boolean: GraphQLBoolean,
|
||||||
ID: GraphQLID,
|
ID: GraphQLID,
|
||||||
}
|
}
|
||||||
if type_ in _scalars:
|
if graphene_type in _scalars:
|
||||||
return _scalars[type_]
|
return _scalars[graphene_type]
|
||||||
|
|
||||||
return GrapheneScalarType(
|
return GrapheneScalarType(
|
||||||
graphene_type=type_,
|
graphene_type=graphene_type,
|
||||||
name=type_._meta.name,
|
name=graphene_type._meta.name,
|
||||||
description=type_._meta.description,
|
description=graphene_type._meta.description,
|
||||||
serialize=getattr(type_, "serialize", None),
|
serialize=getattr(graphene_type, "serialize", None),
|
||||||
parse_value=getattr(type_, "parse_value", None),
|
parse_value=getattr(graphene_type, "parse_value", None),
|
||||||
parse_literal=getattr(type_, "parse_literal", None),
|
parse_literal=getattr(graphene_type, "parse_literal", None),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def construct_enum(type_):
|
def create_enum(graphene_type):
|
||||||
values = {}
|
values = {}
|
||||||
for name, value in type_._meta.enum.__members__.items():
|
for name, value in graphene_type._meta.enum.__members__.items():
|
||||||
description = getattr(value, "description", None)
|
description = getattr(value, "description", None)
|
||||||
deprecation_reason = getattr(value, "deprecation_reason", None)
|
deprecation_reason = getattr(value, "deprecation_reason", None)
|
||||||
if not description and callable(type_._meta.description):
|
if not description and callable(graphene_type._meta.description):
|
||||||
description = type_._meta.description(value)
|
description = graphene_type._meta.description(value)
|
||||||
|
|
||||||
if not deprecation_reason and callable(type_._meta.deprecation_reason):
|
if not deprecation_reason and callable(
|
||||||
deprecation_reason = type_._meta.deprecation_reason(value)
|
graphene_type._meta.deprecation_reason
|
||||||
|
):
|
||||||
|
deprecation_reason = graphene_type._meta.deprecation_reason(value)
|
||||||
|
|
||||||
values[name] = GraphQLEnumValue(
|
values[name] = GraphQLEnumValue(
|
||||||
value=value.value,
|
value=value.value,
|
||||||
|
@ -225,107 +182,98 @@ class GrapheneGraphQLSchema(GraphQLSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
type_description = (
|
type_description = (
|
||||||
type_._meta.description(None)
|
graphene_type._meta.description(None)
|
||||||
if callable(type_._meta.description)
|
if callable(graphene_type._meta.description)
|
||||||
else type_._meta.description
|
else graphene_type._meta.description
|
||||||
)
|
)
|
||||||
|
|
||||||
return GrapheneEnumType(
|
return GrapheneEnumType(
|
||||||
graphene_type=type_,
|
graphene_type=graphene_type,
|
||||||
values=values,
|
values=values,
|
||||||
name=type_._meta.name,
|
name=graphene_type._meta.name,
|
||||||
description=type_description,
|
description=type_description,
|
||||||
)
|
)
|
||||||
|
|
||||||
def construct_objecttype(self, map_, type_):
|
def create_objecttype(self, graphene_type):
|
||||||
if type_._meta.name in map_:
|
create_graphql_type = self.add_type
|
||||||
_type = map_[type_._meta.name]
|
|
||||||
if isinstance(_type, GrapheneGraphQLType):
|
|
||||||
assert _type.graphene_type == type_, (
|
|
||||||
"Found different types with the same name in the schema: {}, {}."
|
|
||||||
).format(_type.graphene_type, type_)
|
|
||||||
return _type
|
|
||||||
|
|
||||||
def interfaces():
|
def interfaces():
|
||||||
interfaces = []
|
interfaces = []
|
||||||
for interface in type_._meta.interfaces:
|
for graphene_interface in graphene_type._meta.interfaces:
|
||||||
self.graphene_reducer(map_, interface)
|
interface = create_graphql_type(graphene_interface)
|
||||||
internal_type = map_[interface._meta.name]
|
assert interface.graphene_type == graphene_interface
|
||||||
assert internal_type.graphene_type == interface
|
interfaces.append(interface)
|
||||||
interfaces.append(internal_type)
|
|
||||||
return interfaces
|
return interfaces
|
||||||
|
|
||||||
if type_._meta.possible_types:
|
if graphene_type._meta.possible_types:
|
||||||
is_type_of = partial(
|
is_type_of = partial(
|
||||||
is_type_of_from_possible_types, type_._meta.possible_types
|
is_type_of_from_possible_types, graphene_type._meta.possible_types
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
is_type_of = type_.is_type_of
|
is_type_of = graphene_type.is_type_of
|
||||||
|
|
||||||
return GrapheneObjectType(
|
return GrapheneObjectType(
|
||||||
graphene_type=type_,
|
graphene_type=graphene_type,
|
||||||
name=type_._meta.name,
|
name=graphene_type._meta.name,
|
||||||
description=type_._meta.description,
|
description=graphene_type._meta.description,
|
||||||
fields=partial(self.construct_fields_for_type, map_, type_),
|
fields=partial(self.create_fields_for_type, graphene_type),
|
||||||
is_type_of=is_type_of,
|
is_type_of=is_type_of,
|
||||||
interfaces=interfaces,
|
interfaces=interfaces,
|
||||||
)
|
)
|
||||||
|
|
||||||
def construct_interface(self, map_, type_):
|
def create_interface(self, graphene_type):
|
||||||
if type_._meta.name in map_:
|
resolve_type = (
|
||||||
_type = map_[type_._meta.name]
|
partial(
|
||||||
if isinstance(_type, GrapheneInterfaceType):
|
self.resolve_type, graphene_type.resolve_type, graphene_type._meta.name
|
||||||
assert _type.graphene_type == type_, (
|
|
||||||
"Found different types with the same name in the schema: {}, {}."
|
|
||||||
).format(_type.graphene_type, type_)
|
|
||||||
return _type
|
|
||||||
|
|
||||||
_resolve_type = None
|
|
||||||
if type_.resolve_type:
|
|
||||||
_resolve_type = partial(
|
|
||||||
resolve_type, type_.resolve_type, map_, type_._meta.name
|
|
||||||
)
|
)
|
||||||
return GrapheneInterfaceType(
|
if graphene_type.resolve_type
|
||||||
graphene_type=type_,
|
else None
|
||||||
name=type_._meta.name,
|
|
||||||
description=type_._meta.description,
|
|
||||||
fields=partial(self.construct_fields_for_type, map_, type_),
|
|
||||||
resolve_type=_resolve_type,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def construct_inputobjecttype(self, map_, type_):
|
return GrapheneInterfaceType(
|
||||||
|
graphene_type=graphene_type,
|
||||||
|
name=graphene_type._meta.name,
|
||||||
|
description=graphene_type._meta.description,
|
||||||
|
fields=partial(self.create_fields_for_type, graphene_type),
|
||||||
|
resolve_type=resolve_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_inputobjecttype(self, graphene_type):
|
||||||
return GrapheneInputObjectType(
|
return GrapheneInputObjectType(
|
||||||
graphene_type=type_,
|
graphene_type=graphene_type,
|
||||||
name=type_._meta.name,
|
name=graphene_type._meta.name,
|
||||||
description=type_._meta.description,
|
description=graphene_type._meta.description,
|
||||||
out_type=type_._meta.container,
|
out_type=graphene_type._meta.container,
|
||||||
fields=partial(
|
fields=partial(
|
||||||
self.construct_fields_for_type, map_, type_, is_input_type=True
|
self.create_fields_for_type, graphene_type, is_input_type=True
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def construct_union(self, map_, type_):
|
def construct_union(self, graphene_type):
|
||||||
_resolve_type = None
|
create_graphql_type = self.add_type
|
||||||
if type_.resolve_type:
|
|
||||||
_resolve_type = partial(
|
|
||||||
resolve_type, type_.resolve_type, map_, type_._meta.name
|
|
||||||
)
|
|
||||||
|
|
||||||
def types():
|
def types():
|
||||||
union_types = []
|
union_types = []
|
||||||
for objecttype in type_._meta.types:
|
for graphene_objecttype in graphene_type._meta.types:
|
||||||
self.graphene_reducer(map_, objecttype)
|
object_type = create_graphql_type(graphene_objecttype)
|
||||||
internal_type = map_[objecttype._meta.name]
|
assert object_type.graphene_type == graphene_objecttype
|
||||||
assert internal_type.graphene_type == objecttype
|
union_types.append(object_type)
|
||||||
union_types.append(internal_type)
|
|
||||||
return union_types
|
return union_types
|
||||||
|
|
||||||
|
resolve_type = (
|
||||||
|
partial(
|
||||||
|
self.resolve_type, graphene_type.resolve_type, graphene_type._meta.name
|
||||||
|
)
|
||||||
|
if graphene_type.resolve_type
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
return GrapheneUnionType(
|
return GrapheneUnionType(
|
||||||
graphene_type=type_,
|
graphene_type=graphene_type,
|
||||||
name=type_._meta.name,
|
name=graphene_type._meta.name,
|
||||||
description=type_._meta.description,
|
description=graphene_type._meta.description,
|
||||||
types=types,
|
types=types,
|
||||||
resolve_type=_resolve_type,
|
resolve_type=resolve_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_name(self, name):
|
def get_name(self, name):
|
||||||
|
@ -333,15 +281,16 @@ class GrapheneGraphQLSchema(GraphQLSchema):
|
||||||
return to_camel_case(name)
|
return to_camel_case(name)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def construct_fields_for_type(self, map_, type_, is_input_type=False):
|
def create_fields_for_type(self, graphene_type, is_input_type=False):
|
||||||
|
create_graphql_type = self.add_type
|
||||||
|
|
||||||
fields = {}
|
fields = {}
|
||||||
for name, field in type_._meta.fields.items():
|
for name, field in graphene_type._meta.fields.items():
|
||||||
if isinstance(field, Dynamic):
|
if isinstance(field, Dynamic):
|
||||||
field = get_field_as(field.get_type(self), _as=Field)
|
field = get_field_as(field.get_type(self), _as=Field)
|
||||||
if not field:
|
if not field:
|
||||||
continue
|
continue
|
||||||
map_ = self.type_map_reducer(map_, field.type)
|
field_type = create_graphql_type(field.type)
|
||||||
field_type = self.get_field_type(map_, field.type)
|
|
||||||
if is_input_type:
|
if is_input_type:
|
||||||
_field = GraphQLInputField(
|
_field = GraphQLInputField(
|
||||||
field_type,
|
field_type,
|
||||||
|
@ -352,17 +301,19 @@ class GrapheneGraphQLSchema(GraphQLSchema):
|
||||||
else:
|
else:
|
||||||
args = {}
|
args = {}
|
||||||
for arg_name, arg in field.args.items():
|
for arg_name, arg in field.args.items():
|
||||||
map_ = self.type_map_reducer(map_, arg.type)
|
arg_type = create_graphql_type(arg.type)
|
||||||
arg_type = self.get_field_type(map_, arg.type)
|
|
||||||
processed_arg_name = arg.name or self.get_name(arg_name)
|
processed_arg_name = arg.name or self.get_name(arg_name)
|
||||||
args[processed_arg_name] = GraphQLArgument(
|
args[processed_arg_name] = GraphQLArgument(
|
||||||
arg_type,
|
arg_type,
|
||||||
out_name=arg_name,
|
out_name=arg_name,
|
||||||
description=arg.description,
|
description=arg.description,
|
||||||
default_value=INVALID
|
default_value=Undefined
|
||||||
if isinstance(arg.type, NonNull)
|
if isinstance(arg.type, NonNull)
|
||||||
else arg.default_value,
|
else arg.default_value,
|
||||||
)
|
)
|
||||||
|
resolve = field.get_resolver(
|
||||||
|
self.get_resolver(graphene_type, name, field.default_value)
|
||||||
|
)
|
||||||
_field = GraphQLField(
|
_field = GraphQLField(
|
||||||
field_type,
|
field_type,
|
||||||
args=args,
|
args=args,
|
||||||
|
@ -392,7 +343,7 @@ class GrapheneGraphQLSchema(GraphQLSchema):
|
||||||
# If we don't find the resolver in the ObjectType class, then try to
|
# If we don't find the resolver in the ObjectType class, then try to
|
||||||
# find it in each of the interfaces
|
# find it in each of the interfaces
|
||||||
interface_resolver = None
|
interface_resolver = None
|
||||||
for interface in type_._meta.interfaces:
|
for interface in graphene_type._meta.interfaces:
|
||||||
if name not in interface._meta.fields:
|
if name not in interface._meta.fields:
|
||||||
continue
|
continue
|
||||||
interface_resolver = getattr(interface, func_name, None)
|
interface_resolver = getattr(interface, func_name, None)
|
||||||
|
@ -404,16 +355,11 @@ class GrapheneGraphQLSchema(GraphQLSchema):
|
||||||
if resolver:
|
if resolver:
|
||||||
return get_unbound_function(resolver)
|
return get_unbound_function(resolver)
|
||||||
|
|
||||||
default_resolver = type_._meta.default_resolver or get_default_resolver()
|
default_resolver = (
|
||||||
|
graphene_type._meta.default_resolver or get_default_resolver()
|
||||||
|
)
|
||||||
return partial(default_resolver, name, default_value)
|
return partial(default_resolver, name, default_value)
|
||||||
|
|
||||||
def get_field_type(self, map_, type_):
|
|
||||||
if isinstance(type_, List):
|
|
||||||
return GraphQLList(self.get_field_type(map_, type_.of_type))
|
|
||||||
if isinstance(type_, NonNull):
|
|
||||||
return GraphQLNonNull(self.get_field_type(map_, type_.of_type))
|
|
||||||
return map_.get(type_._meta.name)
|
|
||||||
|
|
||||||
|
|
||||||
class Schema:
|
class Schema:
|
||||||
"""Schema Definition.
|
"""Schema Definition.
|
||||||
|
@ -423,17 +369,17 @@ class Schema:
|
||||||
questions about the types through introspection.
|
questions about the types through introspection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query (ObjectType): Root query *ObjectType*. Describes entry point for fields to *read*
|
query (Type[ObjectType]): Root query *ObjectType*. Describes entry point for fields to *read*
|
||||||
data in your Schema.
|
data in your Schema.
|
||||||
mutation (ObjectType, optional): Root mutation *ObjectType*. Describes entry point for
|
mutation (Optional[Type[ObjectType]]): Root mutation *ObjectType*. Describes entry point for
|
||||||
fields to *create, update or delete* data in your API.
|
fields to *create, update or delete* data in your API.
|
||||||
subscription (ObjectType, optional): Root subscription *ObjectType*. Describes entry point
|
subscription (Optional[Type[ObjectType]]): Root subscription *ObjectType*. Describes entry point
|
||||||
for fields to receive continuous updates.
|
for fields to receive continuous updates.
|
||||||
|
types (Optional[List[Type[ObjectType]]]): List of any types to include in schema that
|
||||||
|
may not be introspected through root types.
|
||||||
directives (List[GraphQLDirective], optional): List of custom directives to include in the
|
directives (List[GraphQLDirective], optional): List of custom directives to include in the
|
||||||
GraphQL schema. Defaults to only include directives defined by GraphQL spec (@include
|
GraphQL schema. Defaults to only include directives defined by GraphQL spec (@include
|
||||||
and @skip) [GraphQLIncludeDirective, GraphQLSkipDirective].
|
and @skip) [GraphQLIncludeDirective, GraphQLSkipDirective].
|
||||||
types (List[GraphQLType], optional): List of any types to include in schema that
|
|
||||||
may not be introspected through root types.
|
|
||||||
auto_camelcase (bool): Fieldnames will be transformed in Schema's TypeMap from snake_case
|
auto_camelcase (bool): Fieldnames will be transformed in Schema's TypeMap from snake_case
|
||||||
to camelCase (preferred by GraphQL standard). Default True.
|
to camelCase (preferred by GraphQL standard). Default True.
|
||||||
"""
|
"""
|
||||||
|
@ -450,13 +396,15 @@ class Schema:
|
||||||
self.query = query
|
self.query = query
|
||||||
self.mutation = mutation
|
self.mutation = mutation
|
||||||
self.subscription = subscription
|
self.subscription = subscription
|
||||||
self.graphql_schema = GrapheneGraphQLSchema(
|
type_map = TypeMap(
|
||||||
query,
|
query, mutation, subscription, types, auto_camelcase=auto_camelcase
|
||||||
mutation,
|
)
|
||||||
subscription,
|
self.graphql_schema = GraphQLSchema(
|
||||||
types,
|
type_map.query,
|
||||||
|
type_map.mutation,
|
||||||
|
type_map.subscription,
|
||||||
|
type_map.types,
|
||||||
directives,
|
directives,
|
||||||
auto_camelcase=auto_camelcase,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -3,7 +3,7 @@ import datetime
|
||||||
import pytz
|
import pytz
|
||||||
from graphql import GraphQLError
|
from graphql import GraphQLError
|
||||||
|
|
||||||
from pytest import fixture, mark
|
from pytest import fixture
|
||||||
|
|
||||||
from ..datetime import Date, DateTime, Time
|
from ..datetime import Date, DateTime, Time
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
|
@ -84,8 +84,9 @@ def test_bad_datetime_query():
|
||||||
assert result.errors and len(result.errors) == 1
|
assert result.errors and len(result.errors) == 1
|
||||||
error = result.errors[0]
|
error = result.errors[0]
|
||||||
assert isinstance(error, GraphQLError)
|
assert isinstance(error, GraphQLError)
|
||||||
assert error.message == (
|
assert (
|
||||||
'Expected type DateTime, found "Some string that\'s not a datetime".'
|
error.message == "DateTime cannot represent value:"
|
||||||
|
' "Some string that\'s not a datetime"'
|
||||||
)
|
)
|
||||||
assert result.data is None
|
assert result.data is None
|
||||||
|
|
||||||
|
@ -97,8 +98,9 @@ def test_bad_date_query():
|
||||||
|
|
||||||
error = result.errors[0]
|
error = result.errors[0]
|
||||||
assert isinstance(error, GraphQLError)
|
assert isinstance(error, GraphQLError)
|
||||||
assert error.message == (
|
assert (
|
||||||
'Expected type Date, found "Some string that\'s not a date".'
|
error.message == "Date cannot represent value:"
|
||||||
|
' "Some string that\'s not a date"'
|
||||||
)
|
)
|
||||||
assert result.data is None
|
assert result.data is None
|
||||||
|
|
||||||
|
@ -110,8 +112,9 @@ def test_bad_time_query():
|
||||||
|
|
||||||
error = result.errors[0]
|
error = result.errors[0]
|
||||||
assert isinstance(error, GraphQLError)
|
assert isinstance(error, GraphQLError)
|
||||||
assert error.message == (
|
assert (
|
||||||
'Expected type Time, found "Some string that\'s not a time".'
|
error.message == "Time cannot represent value:"
|
||||||
|
' "Some string that\'s not a time"'
|
||||||
)
|
)
|
||||||
assert result.data is None
|
assert result.data is None
|
||||||
|
|
||||||
|
@ -174,9 +177,6 @@ def test_time_query_variable(sample_time):
|
||||||
assert result.data == {"time": isoformat}
|
assert result.data == {"time": isoformat}
|
||||||
|
|
||||||
|
|
||||||
@mark.xfail(
|
|
||||||
reason="creating the error message fails when un-parsable object is not JSON serializable."
|
|
||||||
)
|
|
||||||
def test_bad_variables(sample_date, sample_datetime, sample_time):
|
def test_bad_variables(sample_date, sample_datetime, sample_time):
|
||||||
def _test_bad_variables(type_, input_):
|
def _test_bad_variables(type_, input_):
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
|
@ -185,8 +185,6 @@ def test_bad_variables(sample_date, sample_datetime, sample_time):
|
||||||
),
|
),
|
||||||
variables={"input": input_},
|
variables={"input": input_},
|
||||||
)
|
)
|
||||||
# when `input` is not JSON serializable formatting the error message in
|
|
||||||
# `graphql.utils.is_valid_value` line 79 fails with a TypeError
|
|
||||||
assert isinstance(result.errors, list)
|
assert isinstance(result.errors, list)
|
||||||
assert len(result.errors) == 1
|
assert len(result.errors) == 1
|
||||||
assert isinstance(result.errors[0], GraphQLError)
|
assert isinstance(result.errors[0], GraphQLError)
|
||||||
|
@ -205,7 +203,6 @@ def test_bad_variables(sample_date, sample_datetime, sample_time):
|
||||||
("DateTime", time),
|
("DateTime", time),
|
||||||
("Date", not_a_date),
|
("Date", not_a_date),
|
||||||
("Date", not_a_date_str),
|
("Date", not_a_date_str),
|
||||||
("Date", now),
|
|
||||||
("Date", time),
|
("Date", time),
|
||||||
("Time", not_a_date),
|
("Time", not_a_date),
|
||||||
("Time", not_a_date_str),
|
("Time", not_a_date_str),
|
||||||
|
|
|
@ -262,17 +262,14 @@ def test_query_input_field():
|
||||||
|
|
||||||
result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', "Source!")
|
result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', "Source!")
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {
|
assert result.data == {"test": '["Source!",{"a_input":{"a_field":"String!"}}]'}
|
||||||
"test": '["Source!",{"a_input":{"a_field":"String!","recursive_field":null}}]'
|
|
||||||
}
|
|
||||||
|
|
||||||
result = test_schema.execute(
|
result = test_schema.execute(
|
||||||
'{ test(aInput: {recursiveField: {aField: "String!"}}) }', "Source!"
|
'{ test(aInput: {recursiveField: {aField: "String!"}}) }', "Source!"
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {
|
assert result.data == {
|
||||||
"test": '["Source!",{"a_input":{"a_field":null,"recursive_field":'
|
"test": '["Source!",{"a_input":{"recursive_field":{"a_field":"String!"}}}]'
|
||||||
'{"a_field":"String!","recursive_field":null}}}]'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -408,7 +405,7 @@ def test_big_list_of_containers_multiple_fields_query_benchmark(benchmark):
|
||||||
|
|
||||||
|
|
||||||
def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark(
|
def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark(
|
||||||
benchmark
|
benchmark,
|
||||||
):
|
):
|
||||||
class Container(ObjectType):
|
class Container(ObjectType):
|
||||||
x = Int()
|
x = Int()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
|
|
||||||
|
from graphql.type import GraphQLObjectType, GraphQLSchema
|
||||||
from graphql.pyutils import dedent
|
from graphql.pyutils import dedent
|
||||||
|
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
|
@ -17,8 +18,13 @@ class Query(ObjectType):
|
||||||
|
|
||||||
|
|
||||||
def test_schema():
|
def test_schema():
|
||||||
schema = Schema(Query).graphql_schema
|
schema = Schema(Query)
|
||||||
assert schema.query_type == schema.get_graphql_type(Query)
|
graphql_schema = schema.graphql_schema
|
||||||
|
assert isinstance(graphql_schema, GraphQLSchema)
|
||||||
|
query_type = graphql_schema.query_type
|
||||||
|
assert isinstance(query_type, GraphQLObjectType)
|
||||||
|
assert query_type.name == "Query"
|
||||||
|
assert query_type.graphene_type is Query
|
||||||
|
|
||||||
|
|
||||||
def test_schema_get_type():
|
def test_schema_get_type():
|
||||||
|
@ -39,13 +45,13 @@ def test_schema_str():
|
||||||
schema = Schema(Query)
|
schema = Schema(Query)
|
||||||
assert str(schema) == dedent(
|
assert str(schema) == dedent(
|
||||||
"""
|
"""
|
||||||
type MyOtherType {
|
|
||||||
field: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
inner: MyOtherType
|
inner: MyOtherType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MyOtherType {
|
||||||
|
field: String
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from pytest import raises
|
|
||||||
|
|
||||||
from graphql.type import (
|
from graphql.type import (
|
||||||
GraphQLArgument,
|
GraphQLArgument,
|
||||||
GraphQLEnumType,
|
GraphQLEnumType,
|
||||||
|
@ -21,13 +19,13 @@ from ..interface import Interface
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..scalars import Int, String
|
from ..scalars import Int, String
|
||||||
from ..structures import List, NonNull
|
from ..structures import List, NonNull
|
||||||
from ..schema import GrapheneGraphQLSchema, resolve_type
|
from ..schema import Schema
|
||||||
|
|
||||||
|
|
||||||
def create_type_map(types, auto_camelcase=True):
|
def create_type_map(types, auto_camelcase=True):
|
||||||
query = GraphQLObjectType("Query", {})
|
query = type("Query", (ObjectType,), {})
|
||||||
schema = GrapheneGraphQLSchema(query, types=types, auto_camelcase=auto_camelcase)
|
schema = Schema(query, types=types, auto_camelcase=auto_camelcase)
|
||||||
return schema.type_map
|
return schema.graphql_schema.type_map
|
||||||
|
|
||||||
|
|
||||||
def test_enum():
|
def test_enum():
|
||||||
|
@ -272,20 +270,3 @@ def test_objecttype_with_possible_types():
|
||||||
assert graphql_type.is_type_of
|
assert graphql_type.is_type_of
|
||||||
assert graphql_type.is_type_of({}, None) is True
|
assert graphql_type.is_type_of({}, None) is True
|
||||||
assert graphql_type.is_type_of(MyObjectType(), None) is False
|
assert graphql_type.is_type_of(MyObjectType(), None) is False
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_type_with_missing_type():
|
|
||||||
class MyObjectType(ObjectType):
|
|
||||||
foo_bar = String()
|
|
||||||
|
|
||||||
class MyOtherObjectType(ObjectType):
|
|
||||||
fizz_buzz = String()
|
|
||||||
|
|
||||||
def resolve_type_func(root, info):
|
|
||||||
return MyOtherObjectType
|
|
||||||
|
|
||||||
type_map = create_type_map([MyObjectType])
|
|
||||||
with raises(AssertionError) as excinfo:
|
|
||||||
resolve_type(resolve_type_func, type_map, "MyOtherObjectType", {}, {}, None)
|
|
||||||
|
|
||||||
assert "MyOtherObjectTyp" in str(excinfo.value)
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import re
|
import re
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
|
||||||
# Adapted from this response in Stackoverflow
|
# Adapted from this response in Stackoverflow
|
||||||
|
@ -18,4 +19,4 @@ def to_snake_case(name):
|
||||||
|
|
||||||
|
|
||||||
def to_const(string):
|
def to_const(string):
|
||||||
return re.sub(r"[\W|^]+", "_", string).upper() # noqa
|
return re.sub(r"[\W|^]+", "_", unidecode(string)).upper()
|
||||||
|
|
|
@ -19,7 +19,6 @@ class SubclassWithMeta_Meta(InitSubclassMeta):
|
||||||
class SubclassWithMeta(metaclass=SubclassWithMeta_Meta):
|
class SubclassWithMeta(metaclass=SubclassWithMeta_Meta):
|
||||||
"""This class improves __init_subclass__ to receive automatically the options from meta"""
|
"""This class improves __init_subclass__ to receive automatically the options from meta"""
|
||||||
|
|
||||||
# We will only have the metaclass in Python 2
|
|
||||||
def __init_subclass__(cls, **meta_options):
|
def __init_subclass__(cls, **meta_options):
|
||||||
"""This method just terminates the super() chain"""
|
"""This method just terminates the super() chain"""
|
||||||
_Meta = getattr(cls, "Meta", None)
|
_Meta = getattr(cls, "Meta", None)
|
||||||
|
|
|
@ -21,3 +21,7 @@ def test_camel_case():
|
||||||
|
|
||||||
def test_to_const():
|
def test_to_const():
|
||||||
assert to_const('snakes $1. on a "#plane') == "SNAKES_1_ON_A_PLANE"
|
assert to_const('snakes $1. on a "#plane') == "SNAKES_1_ON_A_PLANE"
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_const_unicode():
|
||||||
|
assert to_const("Skoða þetta unicode stöff") == "SKODA_THETTA_UNICODE_STOFF"
|
||||||
|
|
35
setup.py
35
setup.py
|
@ -45,20 +45,21 @@ class PyTest(TestCommand):
|
||||||
|
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
"pytest",
|
"pytest>=5.3,<6",
|
||||||
"pytest-benchmark",
|
"pytest-benchmark>=3.2,<4",
|
||||||
"pytest-cov",
|
"pytest-cov>=2.8,<3",
|
||||||
"pytest-mock",
|
"pytest-mock>=2,<3",
|
||||||
"pytest-asyncio",
|
"pytest-asyncio>=0.10,<2",
|
||||||
"snapshottest",
|
"snapshottest>=0.5,<1",
|
||||||
"coveralls",
|
"coveralls>=1.11,<2",
|
||||||
"promise",
|
"promise>=2.3,<3",
|
||||||
"six",
|
"mock>=4.0,<5",
|
||||||
"mock",
|
"pytz==2019.3",
|
||||||
"pytz",
|
"iso8601>=0.1,<2",
|
||||||
"iso8601",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
dev_requires = ["black==19.10b0", "flake8>=3.7,<4"] + tests_require
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="graphene",
|
name="graphene",
|
||||||
version=version,
|
version=version,
|
||||||
|
@ -76,15 +77,17 @@ setup(
|
||||||
"Topic :: Software Development :: Libraries",
|
"Topic :: Software Development :: Libraries",
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
],
|
],
|
||||||
keywords="api graphql protocol rest relay graphene",
|
keywords="api graphql protocol rest relay graphene",
|
||||||
packages=find_packages(exclude=["tests", "tests.*", "examples"]),
|
packages=find_packages(exclude=["tests", "tests.*", "examples"]),
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"graphql-core>=3.0.0a0,<4",
|
"graphql-core>=3.1.0b1,<4",
|
||||||
"graphql-relay>=3.0.0a0,<4",
|
"graphql-relay>=3.0,<4",
|
||||||
"aniso8601>=6,<8",
|
"aniso8601>=8,<9",
|
||||||
|
"unidecode>=1.1.1,<2",
|
||||||
],
|
],
|
||||||
tests_require=tests_require,
|
tests_require=tests_require,
|
||||||
extras_require={"test": tests_require},
|
extras_require={"test": tests_require, "dev": dev_requires},
|
||||||
cmdclass={"test": PyTest},
|
cmdclass={"test": PyTest},
|
||||||
)
|
)
|
||||||
|
|
|
@ -42,11 +42,11 @@ class OtherMutation(ClientIDMutation):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate_and_get_payload(
|
def mutate_and_get_payload(
|
||||||
self, info, shared, additional_field, client_mutation_id=None
|
self, info, shared="", additional_field="", client_mutation_id=None
|
||||||
):
|
):
|
||||||
edge_type = MyEdge
|
edge_type = MyEdge
|
||||||
return OtherMutation(
|
return OtherMutation(
|
||||||
name=(shared or "") + (additional_field or ""),
|
name=shared + additional_field,
|
||||||
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
|
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
10
tox.ini
10
tox.ini
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = flake8,py36,py37,pre-commit,mypy
|
envlist = flake8,py36,py37,py38,pre-commit,mypy
|
||||||
skipsdist = true
|
skipsdist = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
@ -8,12 +8,12 @@ deps =
|
||||||
setenv =
|
setenv =
|
||||||
PYTHONPATH = .:{envdir}
|
PYTHONPATH = .:{envdir}
|
||||||
commands =
|
commands =
|
||||||
py{36,37}: py.test --cov=graphene graphene examples tests_asyncio {posargs}
|
py{36,37}: pytest --cov=graphene graphene examples tests_asyncio {posargs}
|
||||||
|
|
||||||
[testenv:pre-commit]
|
[testenv:pre-commit]
|
||||||
basepython=python3.7
|
basepython=python3.7
|
||||||
deps =
|
deps =
|
||||||
pre-commit>0.12.0
|
pre-commit>=2,<3
|
||||||
setenv =
|
setenv =
|
||||||
LC_CTYPE=en_US.UTF-8
|
LC_CTYPE=en_US.UTF-8
|
||||||
commands =
|
commands =
|
||||||
|
@ -22,12 +22,12 @@ commands =
|
||||||
[testenv:mypy]
|
[testenv:mypy]
|
||||||
basepython=python3.7
|
basepython=python3.7
|
||||||
deps =
|
deps =
|
||||||
mypy>=0.720
|
mypy>=0.761,<1
|
||||||
commands =
|
commands =
|
||||||
mypy graphene
|
mypy graphene
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
basepython=python3.6
|
basepython=python3.7
|
||||||
deps =
|
deps =
|
||||||
flake8>=3.7,<4
|
flake8>=3.7,<4
|
||||||
commands =
|
commands =
|
||||||
|
|
Loading…
Reference in New Issue
Block a user