Merge pull request #500 from graphql-python/2.0

[WIP] Road to 2.0
This commit is contained in:
Syrus Akbary 2017-08-27 13:27:12 -07:00 committed by GitHub
commit 0b92d3dba6
103 changed files with 2707 additions and 1061 deletions

1
.gitignore vendored
View File

@ -80,3 +80,4 @@ target/
# Databases
*.sqlite3
.vscode
.mypy_cache

View File

@ -2,8 +2,8 @@ language: python
sudo: false
python:
- 2.7
- 3.4
- 3.5
- 3.6
- pypy
before_install:
- |
@ -26,6 +26,8 @@ install:
python setup.py develop
elif [ "$TEST_TYPE" = lint ]; then
pip install flake8
elif [ "$TEST_TYPE" = mypy ]; then
pip install mypy
fi
script:
- |
@ -33,6 +35,10 @@ script:
echo "Checking Python code lint."
flake8 graphene
exit
elif [ "$TEST_TYPE" = mypy ]; then
echo "Checking Python types."
mypy graphene
exit
elif [ "$TEST_TYPE" = build ]; then
py.test --cov=graphene graphene examples
fi
@ -51,6 +57,8 @@ matrix:
include:
- python: '2.7'
env: TEST_TYPE=lint
- python: '3.6'
env: TEST_TYPE=mypy
deploy:
provider: pypi
user: syrusakbary
@ -58,3 +66,4 @@ deploy:
tags: true
password:
secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ=
distributions: "sdist bdist_wheel"

View File

@ -1,4 +1,4 @@
Please read [UPGRADE-v1.0.md](/UPGRADE-v1.0.md) to learn how to upgrade to Graphene `1.0`.
Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade to Graphene `2.0`.
---
@ -32,12 +32,12 @@ Also, Graphene is fully compatible with the GraphQL spec, working seamlessly wit
For instaling graphene, just run this command in your shell
```bash
pip install "graphene>=1.0"
pip install "graphene>=2.0.dev"
```
## 1.0 Upgrade Guide
## 2.0 Upgrade Guide
Please read [UPGRADE-v1.0.md](/UPGRADE-v1.0.md) to learn how to upgrade.
Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade.
## Examples
@ -48,7 +48,7 @@ Here is one example for you to get started:
class Query(graphene.ObjectType):
hello = graphene.String(description='A typical hello world')
def resolve_hello(self, args, context, info):
def resolve_hello(self, info):
return 'World'
schema = graphene.Schema(query=Query)

View File

@ -1,5 +1,5 @@
Please read `UPGRADE-v1.0.md </UPGRADE-v1.0.md>`__ to learn how to
upgrade to Graphene ``1.0``.
Please read `UPGRADE-v2.0.md </UPGRADE-v2.0.md>`__ to learn how to
upgrade to Graphene ``2.0``.
--------------
@ -11,7 +11,7 @@ building GraphQL schemas/types fast and easily.
- **Easy to use:** Graphene helps you use GraphQL in Python without
effort.
- **Relay:** Graphene has builtin support for both Relay.
- **Relay:** Graphene has builtin support for Relay.
- **Data agnostic:** Graphene supports any kind of data source: SQL
(Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe
that by providing a complete API you could plug Graphene anywhere
@ -47,12 +47,12 @@ For instaling graphene, just run this command in your shell
.. code:: bash
pip install "graphene>=1.0"
pip install "graphene>=2.0"
1.0 Upgrade Guide
2.0 Upgrade Guide
-----------------
Please read `UPGRADE-v1.0.md </UPGRADE-v1.0.md>`__ to learn how to
Please read `UPGRADE-v2.0.md </UPGRADE-v2.0.md>`__ to learn how to
upgrade.
Examples
@ -65,7 +65,7 @@ Here is one example for you to get started:
class Query(graphene.ObjectType):
hello = graphene.String(description='A typical hello world')
def resolve_hello(self, args, context, info):
def resolve_hello(self, info):
return 'World'
schema = graphene.Schema(query=Query)

293
UPGRADE-v2.0.md Normal file
View File

@ -0,0 +1,293 @@
# v2.0 Upgrade Guide
`ObjectType`, `Interface`, `InputObjectType`, `Scalar` and `Enum` implementations
have been quite simplified, without the need to define a explicit Metaclass for each subtype.
It also improves the field resolvers, [simplifying the code](#simpler-resolvers) the
developer has to write to use them.
**Deprecations:**
* [`AbstractType`](#abstracttype-deprecated)
* [`resolve_only_args`](#resolve_only_args)
* [`Mutation.Input`](#mutationinput)
**Breaking changes:**
* [`Simpler Resolvers`](#simpler-resolvers)
* [`Node Connections`](#node-connections)
**New Features!**
* [`InputObjectType`](#inputobjecttype)
* [`Meta as Class arguments`](#meta-ass-class-arguments) (_only available for Python 3_)
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/2.0/graphene/tests/issues/test_425.py).
## Deprecations
### AbstractType deprecated
AbstractType is deprecated in graphene 2.0, you can now use normal inheritance instead.
Before:
```python
class CommonFields(AbstractType):
name = String()
class Pet(CommonFields, Interface):
pass
```
With 2.0:
```python
class CommonFields(object):
name = String()
class Pet(CommonFields, Interface):
pass
```
### resolve\_only\_args
`resolve_only_args` is now deprecated as the resolver API has been simplified.
Before:
```python
class User(ObjectType):
name = String()
@resolve_only_args
def resolve_name(self):
return self.name
```
With 2.0:
```python
class User(ObjectType):
name = String()
def resolve_name(self, info):
return self.name
```
### Mutation.Input
`Mutation.Input` is now deprecated in favor of using `Mutation.Arguments` (`ClientIDMutation` still uses `Input`).
Before:
```python
class User(Mutation):
class Input:
name = String()
```
With 2.0:
```python
class User(Mutation):
class Arguments:
name = String()
```
## Breaking Changes
### Simpler resolvers
All the resolvers in graphene have been simplified.
Prior to Graphene `2.0`, all resolvers required four arguments: `(root, args, context, info)`.
Now, resolver `args` are passed as keyword arguments to the function, and `context` argument dissapeared in favor of `info.context`.
Before:
```python
my_field = graphene.String(my_arg=graphene.String())
def resolve_my_field(self, args, context, info):
my_arg = args.get('my_arg')
return ...
```
With 2.0:
```python
my_field = graphene.String(my_arg=graphene.String())
def resolve_my_field(self, info, my_arg):
return ...
```
And, if you need the context in the resolver, you can use `info.context`:
```python
my_field = graphene.String(my_arg=graphene.String())
def resolve_my_field(self, info, my_arg):
context = info.context
return ...
```
### Node Connections
Node types no longer have a `Connection` by default.
In 2.0 and onwards `Connection`s should be defined explicitly.
Before:
```python
class User(ObjectType):
class Meta:
interfaces = [relay.Node]
name = String()
class Query(ObjectType):
user_connection = relay.ConnectionField(User)
```
With 2.0:
```python
class User(ObjectType):
class Meta:
interfaces = [relay.Node]
name = String()
class UserConnection(relay.Connection):
class Meta:
node = User
class Query(ObjectType):
user_connection = relay.ConnectionField(UserConnection)
```
## Node.get_node
The method `get_node` in `ObjectTypes` that have `Node` as interface, changes its API.
From `def get_node(cls, id, context, info)` to `def get_node(cls, info, id)`.
```python
class MyObject(ObjectType):
class Meta:
interfaces = (Node, )
@classmethod
def get_node(cls, id, context, info):
return ...
```
To:
```python
class MyObject(ObjectType):
class Meta:
interfaces = (Node, )
@classmethod
def get_node(cls, info, id):
return ...
```
## Mutation.mutate
Now only receives (`root`, `info`, `**args`)
## ClientIDMutation.mutate_and_get_payload
Now only receives (`root`, `info`, `**input`)
## New Features
### InputObjectType
If you are using `InputObjectType`, you now can access
its fields via `getattr` (`my_input.myattr`) when resolving, instead of
the classic way `my_input['myattr']`.
And also use custom defined properties on your input class.
Example. Before:
```python
class UserInput(InputObjectType):
id = ID(required=True)
def is_valid_input(input):
return input.get('id').startswith('userid_')
class Query(ObjectType):
user = graphene.Field(User, input=UserInput())
@resolve_only_args
def resolve_user(self, input):
user_id = input.get('id')
if is_valid_input(user_id):
return get_user(user_id)
```
With 2.0:
```python
class UserInput(InputObjectType):
id = ID(required=True)
@property
def is_valid(self):
return self.id.startswith('userid_')
class Query(ObjectType):
user = graphene.Field(User, input=UserInput())
def resolve_user(self, info, input):
if input.is_valid:
return get_user(input.id)
```
### Meta as Class arguments
Now you can use the meta options as class arguments (**ONLY PYTHON 3**).
Before:
```python
class Dog(ObjectType):
class Meta:
interfaces = [Pet]
name = String()
```
With 2.0:
```python
class Dog(ObjectType, interfaces=[Pet]):
name = String()
```
### Abstract types
Now you can create abstact types super easily, without the need of subclassing the meta.
```python
class Base(ObjectType):
class Meta:
abstract = True
id = ID()
def resolve_id(self, info):
return "{type}_{id}".format(
type=self.__class__.__name__,
id=self.id
)
```
### UUID Scalar
In Graphene 2.0 there is a new dedicated scalar for UUIDs, `UUID`.

View File

@ -99,8 +99,8 @@ leaner code and at most 4 database requests, and possibly fewer if there are cac
best_friend = graphene.Field(lambda: User)
friends = graphene.List(lambda: User)
def resolve_best_friend(self, args, context, info):
def resolve_best_friend(self, info):
return user_loader.load(self.best_friend_id)
def resolve_friends(self, args, context, info):
def resolve_friends(self, info):
return user_loader.load_many(self.friend_ids)

View File

@ -24,8 +24,8 @@ You can pass context to a query via ``context_value``.
class Query(graphene.ObjectType):
name = graphene.String()
def resolve_name(self, args, context, info):
return context.get('name')
def resolve_name(self, info):
return info.context.get('name')
schema = graphene.Schema(Query)
result = schema.execute('{ name }', context_value={'name': 'Syrus'})
@ -43,8 +43,8 @@ You can pass variables to a query via ``variable_values``.
class Query(graphene.ObjectType):
user = graphene.Field(User)
def resolve_user(self, args, context, info):
return context.get('user')
def resolve_user(self, info):
return info.context.get('user')
schema = graphene.Schema(Query)
result = schema.execute(

View File

@ -31,10 +31,10 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'`
.. code:: python
class AuthorizationMiddleware(object):
def resolve(self, next, root, args, context, info):
def resolve(self, next, root, info, **args):
if info.field_name == 'user':
return None
return next(root, args, context, info)
return next(root, info, **args)
And then execute it with:

View File

@ -12,15 +12,15 @@ Lets build a basic GraphQL schema from scratch.
Requirements
------------
- Python (2.7, 3.2, 3.3, 3.4, 3.5, pypy)
- Graphene (1.0)
- Python (2.7, 3.4, 3.5, 3.6, pypy)
- Graphene (2.0)
Project setup
-------------
.. code:: bash
pip install "graphene>=1.0"
pip install "graphene>=2.0"
Creating a basic Schema
-----------------------
@ -37,10 +37,10 @@ one field: ``hello`` and an input name. And when we query it, it should return `
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.Argument(graphene.String, default_value="stranger"))
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, args, context, info):
return 'Hello ' + args['name']
def resolve_hello(self, info, name):
return 'Hello ' + name
schema = graphene.Schema(query=Query)

View File

@ -41,5 +41,5 @@ that implements ``Node`` will have a default Connection.
name = graphene.String()
ships = relay.ConnectionField(ShipConnection)
def resolve_ships(self, args, context, info):
def resolve_ships(self, info):
return []

View File

@ -21,9 +21,9 @@ subclass of ``relay.ClientIDMutation``.
faction = graphene.Field(Faction)
@classmethod
def mutate_and_get_payload(cls, input, context, info):
ship_name = input.get('ship_name')
faction_id = input.get('faction_id')
def mutate_and_get_payload(cls, root, info, **input):
ship_name = input.ship_name
faction_id = input.faction_id
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
return IntroduceShip(ship=ship, faction=faction)
@ -46,7 +46,7 @@ Mutations can also accept files, that's how it will work with different integrat
success = graphene.String()
@classmethod
def mutate_and_get_payload(cls, input, context, info):
def mutate_and_get_payload(cls, root, info, **input):
# When using it in Django, context will be the request
files = context.FILES
# Or, if used in Flask, context will be the flask global request

View File

@ -22,7 +22,7 @@ Example usage (taken from the `Starwars Relay example`_):
name = graphene.String(description='The name of the ship.')
@classmethod
def get_node(cls, id, context, info):
def get_node(cls, info, id):
return get_ship(id)
The ``id`` returned by the ``Ship`` type when you query it will be a
@ -55,7 +55,7 @@ Example of a custom node:
return '{}:{}'.format(type, id)
@staticmethod
def get_node_from_global_id(global_id, context, info, only_type=None):
def get_node_from_global_id(info global_id, only_type=None):
type, id = global_id.split(':')
if only_node:
# We assure that the node type that we want to retrieve

View File

@ -13,15 +13,14 @@ This example defines a Mutation:
import graphene
class CreatePerson(graphene.Mutation):
class Input:
class Arguments:
name = graphene.String()
ok = graphene.Boolean()
person = graphene.Field(lambda: Person)
@staticmethod
def mutate(root, args, context, info):
person = Person(name=args.get('name'))
def mutate(self, name):
person = Person(name=name)
ok = True
return CreatePerson(person=person, ok=ok)
@ -90,30 +89,29 @@ InputFields are used in mutations to allow nested input data for mutations
To use an InputField you define an InputObjectType that specifies the structure of your input data
.. code:: python
import graphene
class PersonInput(graphene.InputObjectType):
name = graphene.String()
age = graphene.Int()
name = graphene.String(required=True)
age = graphene.Int(required=True)
class CreatePerson(graphene.Mutation):
class Input:
person_data = graphene.Argument(PersonInput)
class Arguments:
person_data = PersonInput(required=True)
person = graphene.Field(lambda: Person)
person = graphene.Field(Person)
@staticmethod
def mutate(root, args, context, info):
p_data = args.get('person_data')
def mutate(root, person_data=None):
name = p_data.get('name')
age = p_data.get('age')
person = Person(name=name, age=age)
person = Person(
name=person_data.name,
age=person_data.age
)
return CreatePerson(person=person)

View File

@ -25,7 +25,7 @@ This example model defines a Person, with a first and a last name:
last_name = graphene.String()
full_name = graphene.String()
def resolve_full_name(self, args, context, info):
def resolve_full_name(self, info):
return '{} {}'.format(self.first_name, self.last_name)
**first\_name** and **last\_name** are fields of the ObjectType. Each
@ -71,8 +71,7 @@ method in the class.
class Query(graphene.ObjectType):
reverse = graphene.String(word=graphene.String())
def resolve_reverse(self, args, context, info):
word = args.get('word')
def resolve_reverse(self, info, word):
return word[::-1]
Resolvers outside the class
@ -84,8 +83,7 @@ A field can use a custom resolver from outside the class:
import graphene
def reverse(root, args, context, info):
word = args.get('word')
def reverse(root, info, word):
return word[::-1]
class Query(graphene.ObjectType):

63
docs/types/unions.rst Normal file
View File

@ -0,0 +1,63 @@
Unions
======
Union types are very similar to interfaces, but they don't get
to specify any common fields between the types.
The basics:
- Each Union is a Python class that inherits from ``graphene.Union``.
- Unions don't have any fields on it, just links to the possible objecttypes.
Quick example
-------------
This example model defines a ``Character`` interface with a name. ``Human``
and ``Droid`` are two implementations of that interface.
.. code:: python
import graphene
class Human(graphene.ObjectType):
name = graphene.String()
born_in = graphene.String()
class Droid(graphene.ObjectType):
name = graphene.String()
primary_function = graphene.String()
class Starship(graphene.ObjectType):
name = graphene.String()
length = graphene.Int()
class SearchResult(graphene.Union):
class Meta:
types = (Human, Droid, Starship)
Wherever we return a SearchResult type in our schema, we might get a Human, a Droid, or a Starship.
Note that members of a union type need to be concrete object types;
you can't create a union type out of interfaces or other unions.
The above types have the following representation in a schema:
.. code::
type Droid {
name: String
primaryFunction: String
}
type Human {
name: String
bornIn: String
}
type Ship {
name: String
length: Int
}
union SearchResult = Human | Droid | Starship

View File

@ -11,10 +11,9 @@ class Address(graphene.ObjectType):
class Query(graphene.ObjectType):
address = graphene.Field(Address, geo=GeoInput())
address = graphene.Field(Address, geo=GeoInput(required=True))
def resolve_address(self, args, context, info):
geo = args.get('geo')
def resolve_address(self, info, geo):
return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng')))

View File

@ -9,8 +9,9 @@ class User(graphene.ObjectType):
class Query(graphene.ObjectType):
me = graphene.Field(User)
def resolve_me(self, args, context, info):
return context['user']
def resolve_me(self, info):
return info.context['user']
schema = graphene.Schema(query=Query)
query = '''

View File

@ -11,9 +11,10 @@ class Query(graphene.ObjectType):
patron = graphene.Field(Patron)
def resolve_patron(self, args, context, info):
def resolve_patron(self, info):
return Patron(id=1, name='Syrus', age=27)
schema = graphene.Schema(query=Query)
query = '''
query something{

View File

@ -1,5 +1,4 @@
import graphene
from graphene import resolve_only_args
from .data import get_character, get_droid, get_hero, get_human
@ -16,7 +15,7 @@ class Character(graphene.Interface):
friends = graphene.List(lambda: Character)
appears_in = graphene.List(Episode)
def resolve_friends(self, args, *_):
def resolve_friends(self, info):
# The character friends is a list of strings
return [get_character(f) for f in self.friends]
@ -46,16 +45,13 @@ class Query(graphene.ObjectType):
id=graphene.String()
)
@resolve_only_args
def resolve_hero(self, episode=None):
def resolve_hero(self, info, episode=None):
return get_hero(episode)
@resolve_only_args
def resolve_human(self, id):
def resolve_human(self, info, id):
return get_human(id)
@resolve_only_args
def resolve_droid(self, id):
def resolve_droid(self, info, id):
return get_droid(id)

View File

@ -1,4 +1,5 @@
from graphene.test import Client
from ..data import setup
from ..schema import schema
@ -6,6 +7,7 @@ setup()
client = Client(schema)
def test_hero_name_query(snapshot):
query = '''
query HeroNameQuery {
@ -15,7 +17,6 @@ def test_hero_name_query(snapshot):
}
'''
snapshot.assert_match(client.execute(query))
def test_hero_name_and_friends_query(snapshot):

View File

@ -1,5 +1,5 @@
import graphene
from graphene import relay, resolve_only_args
from graphene import relay
from .data import create_ship, get_empire, get_faction, get_rebels, get_ship
@ -13,10 +13,16 @@ class Ship(graphene.ObjectType):
name = graphene.String(description='The name of the ship.')
@classmethod
def get_node(cls, id, context, info):
def get_node(cls, info, id):
return get_ship(id)
class ShipConnection(relay.Connection):
class Meta:
node = Ship
class Faction(graphene.ObjectType):
'''A faction in the Star Wars saga'''
@ -24,15 +30,14 @@ class Faction(graphene.ObjectType):
interfaces = (relay.Node, )
name = graphene.String(description='The name of the faction.')
ships = relay.ConnectionField(Ship, description='The ships used by the faction.')
ships = relay.ConnectionField(ShipConnection, description='The ships used by the faction.')
@resolve_only_args
def resolve_ships(self, **args):
def resolve_ships(self, info, **args):
# Transform the instance ship_ids into real instances
return [get_ship(ship_id) for ship_id in self.ships]
@classmethod
def get_node(cls, id, context, info):
def get_node(cls, info, id):
return get_faction(id)
@ -46,9 +51,7 @@ class IntroduceShip(relay.ClientIDMutation):
faction = graphene.Field(Faction)
@classmethod
def mutate_and_get_payload(cls, input, context, info):
ship_name = input.get('ship_name')
faction_id = input.get('faction_id')
def mutate_and_get_payload(cls, root, info, ship_name, faction_id, client_mutation_id=None):
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
return IntroduceShip(ship=ship, faction=faction)
@ -59,12 +62,10 @@ class Query(graphene.ObjectType):
empire = graphene.Field(Faction)
node = relay.Node.Field()
@resolve_only_args
def resolve_rebels(self):
def resolve_rebels(self, info):
return get_rebels()
@resolve_only_args
def resolve_empire(self):
def resolve_empire(self, info):
return get_empire()

View File

@ -51,3 +51,63 @@ snapshots['test_correctly_refetches_xwing 1'] = {
}
}
}
snapshots['test_str_schema 1'] = '''schema {
query: Query
mutation: Mutation
}
type Faction implements Node {
id: ID!
name: String
ships(before: String, after: String, first: Int, last: Int): ShipConnection
}
input IntroduceShipInput {
shipName: String!
factionId: String!
clientMutationId: String
}
type IntroduceShipPayload {
ship: Ship
faction: Faction
clientMutationId: String
}
type Mutation {
introduceShip(input: IntroduceShipInput!): IntroduceShipPayload
}
interface Node {
id: ID!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
rebels: Faction
empire: Faction
node(id: ID!): Node
}
type Ship implements Node {
id: ID!
name: String
}
type ShipConnection {
pageInfo: PageInfo!
edges: [ShipEdge]!
}
type ShipEdge {
node: Ship
cursor: String!
}
'''

View File

@ -1,4 +1,5 @@
from graphene.test import Client
from ..data import setup
from ..schema import schema

View File

@ -1,4 +1,5 @@
from graphene.test import Client
from ..data import setup
from ..schema import schema

View File

@ -1,4 +1,5 @@
from graphene.test import Client
from ..data import setup
from ..schema import schema
@ -7,66 +8,8 @@ setup()
client = Client(schema)
def test_str_schema():
assert str(schema) == '''schema {
query: Query
mutation: Mutation
}
type Faction implements Node {
id: ID!
name: String
ships(before: String, after: String, first: Int, last: Int): ShipConnection
}
input IntroduceShipInput {
shipName: String!
factionId: String!
clientMutationId: String
}
type IntroduceShipPayload {
ship: Ship
faction: Faction
clientMutationId: String
}
type Mutation {
introduceShip(input: IntroduceShipInput!): IntroduceShipPayload
}
interface Node {
id: ID!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
rebels: Faction
empire: Faction
node(id: ID!): Node
}
type Ship implements Node {
id: ID!
name: String
}
type ShipConnection {
pageInfo: PageInfo!
edges: [ShipEdge]!
}
type ShipEdge {
node: Ship
cursor: String!
}
'''
def test_str_schema(snapshot):
snapshot.assert_match(str(schema))
def test_correctly_fetches_id_name_rebels(snapshot):

View File

@ -1,78 +1,78 @@
from .pyutils.version import get_version
try:
# This variable is injected in the __builtins__ by the build
# process. It used to enable importing subpackages when
# the required packages are not installed
__SETUP__
except NameError:
__SETUP__ = False
from .types import (
AbstractType,
ObjectType,
InputObjectType,
Interface,
Mutation,
Field,
InputField,
Schema,
Scalar,
String, ID, Int, Float, Boolean,
JSONString,
UUID,
List, NonNull,
Enum,
Argument,
Dynamic,
Union,
Context,
ResolveInfo
)
from .relay import (
Node,
is_node,
GlobalID,
ClientIDMutation,
Connection,
ConnectionField,
PageInfo
)
from .utils.resolve_only_args import resolve_only_args
from .utils.module_loading import lazy_import
VERSION = (1, 4, 1, 'final', 0)
VERSION = (2, 0, 0, 'alpha', 0)
__version__ = get_version(VERSION)
if not __SETUP__:
__all__ = [
'__version__',
'ObjectType',
'InputObjectType',
'Interface',
'Mutation',
'Field',
'InputField',
'Schema',
'Scalar',
'String',
'ID',
'Int',
'Float',
'Enum',
'Boolean',
'JSONString',
'UUID',
'List',
'NonNull',
'Argument',
'Dynamic',
'Union',
'resolve_only_args',
'Node',
'is_node',
'GlobalID',
'ClientIDMutation',
'Connection',
'ConnectionField',
'PageInfo',
'lazy_import',
'Context',
'ResolveInfo',
from .types import (
AbstractType,
ObjectType,
InputObjectType,
Interface,
Mutation,
Field,
InputField,
Schema,
Scalar,
String, ID, Int, Float, Boolean,
List, NonNull,
Enum,
Argument,
Dynamic,
Union,
)
from .relay import (
Node,
is_node,
GlobalID,
ClientIDMutation,
Connection,
ConnectionField,
PageInfo
)
from .utils.resolve_only_args import resolve_only_args
from .utils.module_loading import lazy_import
__all__ = [
'AbstractType',
'ObjectType',
'InputObjectType',
'Interface',
'Mutation',
'Field',
'InputField',
'Schema',
'Scalar',
'String',
'ID',
'Int',
'Float',
'Enum',
'Boolean',
'List',
'NonNull',
'Argument',
'Dynamic',
'Union',
'resolve_only_args',
'Node',
'is_node',
'GlobalID',
'ClientIDMutation',
'Connection',
'ConnectionField',
'PageInfo',
'lazy_import',
]
# Deprecated
'AbstractType',
]

View File

@ -0,0 +1,19 @@
from __future__ import absolute_import
import six
try:
from enum import Enum
except ImportError:
from .enum import Enum
try:
from inspect import signature
except ImportError:
from .signature import signature
if six.PY2:
def func_name(func):
return func.func_name
else:
def func_name(func):
return func.__name__

View File

@ -0,0 +1,19 @@
is_init_subclass_available = hasattr(object, '__init_subclass__')
if not is_init_subclass_available:
class InitSubclassMeta(type):
"""Metaclass that implements PEP 487 protocol"""
def __new__(cls, name, bases, ns, **kwargs):
__init_subclass__ = ns.pop('__init_subclass__', None)
if __init_subclass__:
__init_subclass__ = classmethod(__init_subclass__)
ns['__init_subclass__'] = __init_subclass__
return super(InitSubclassMeta, cls).__new__(cls, name, bases, ns, **kwargs)
def __init__(cls, name, bases, ns, **kwargs):
super(InitSubclassMeta, cls).__init__(name, bases, ns)
super_class = super(cls, cls)
if hasattr(super_class, '__init_subclass__'):
super_class.__init_subclass__.__func__(cls, **kwargs)
else:
InitSubclassMeta = type # type: ignore

View File

@ -0,0 +1,808 @@
# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
"""Function signature objects for callables
Back port of Python 3.3's function signature tools from the inspect module,
modified to be compatible with Python 2.7 and 3.2+.
"""
from __future__ import absolute_import, division, print_function
import itertools
import functools
import re
import types
from collections import OrderedDict
__version__ = "0.4"
__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
_WrapperDescriptor = type(type.__call__)
_MethodWrapper = type(all.__call__)
_NonUserDefinedCallables = (_WrapperDescriptor,
_MethodWrapper,
types.BuiltinFunctionType)
def formatannotation(annotation, base_module=None):
if isinstance(annotation, type):
if annotation.__module__ in ('builtins', '__builtin__', base_module):
return annotation.__name__
return annotation.__module__+'.'+annotation.__name__
return repr(annotation)
def _get_user_defined_method(cls, method_name, *nested):
try:
if cls is type:
return
meth = getattr(cls, method_name)
for name in nested:
meth = getattr(meth, name, meth)
except AttributeError:
return
else:
if not isinstance(meth, _NonUserDefinedCallables):
# Once '__signature__' will be added to 'C'-level
# callables, this check won't be necessary
return meth
def signature(obj):
'''Get a signature object for the passed callable.'''
if not callable(obj):
raise TypeError('{0!r} is not a callable object'.format(obj))
if isinstance(obj, types.MethodType):
sig = signature(obj.__func__)
if obj.__self__ is None:
# Unbound method: the first parameter becomes positional-only
if sig.parameters:
first = sig.parameters.values()[0].replace(
kind=_POSITIONAL_ONLY)
return sig.replace(
parameters=(first,) + tuple(sig.parameters.values())[1:])
else:
return sig
else:
# In this case we skip the first parameter of the underlying
# function (usually `self` or `cls`).
return sig.replace(parameters=tuple(sig.parameters.values())[1:])
try:
sig = obj.__signature__
except AttributeError:
pass
else:
if sig is not None:
return sig
try:
# Was this function wrapped by a decorator?
wrapped = obj.__wrapped__
except AttributeError:
pass
else:
return signature(wrapped)
if isinstance(obj, types.FunctionType):
return Signature.from_function(obj)
if isinstance(obj, functools.partial):
sig = signature(obj.func)
new_params = OrderedDict(sig.parameters.items())
partial_args = obj.args or ()
partial_keywords = obj.keywords or {}
try:
ba = sig.bind_partial(*partial_args, **partial_keywords)
except TypeError as ex:
msg = 'partial object {0!r} has incorrect arguments'.format(obj)
raise ValueError(msg)
for arg_name, arg_value in ba.arguments.items():
param = new_params[arg_name]
if arg_name in partial_keywords:
# We set a new default value, because the following code
# is correct:
#
# >>> def foo(a): print(a)
# >>> print(partial(partial(foo, a=10), a=20)())
# 20
# >>> print(partial(partial(foo, a=10), a=20)(a=30))
# 30
#
# So, with 'partial' objects, passing a keyword argument is
# like setting a new default value for the corresponding
# parameter
#
# We also mark this parameter with '_partial_kwarg'
# flag. Later, in '_bind', the 'default' value of this
# parameter will be added to 'kwargs', to simulate
# the 'functools.partial' real call.
new_params[arg_name] = param.replace(default=arg_value,
_partial_kwarg=True)
elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
not param._partial_kwarg):
new_params.pop(arg_name)
return sig.replace(parameters=new_params.values())
sig = None
if isinstance(obj, type):
# obj is a class or a metaclass
# First, let's see if it has an overloaded __call__ defined
# in its metaclass
call = _get_user_defined_method(type(obj), '__call__')
if call is not None:
sig = signature(call)
else:
# Now we check if the 'obj' class has a '__new__' method
new = _get_user_defined_method(obj, '__new__')
if new is not None:
sig = signature(new)
else:
# Finally, we should have at least __init__ implemented
init = _get_user_defined_method(obj, '__init__')
if init is not None:
sig = signature(init)
elif not isinstance(obj, _NonUserDefinedCallables):
# An object with __call__
# We also check that the 'obj' is not an instance of
# _WrapperDescriptor or _MethodWrapper to avoid
# infinite recursion (and even potential segfault)
call = _get_user_defined_method(type(obj), '__call__', 'im_func')
if call is not None:
sig = signature(call)
if sig is not None:
# For classes and objects we skip the first parameter of their
# __call__, __new__, or __init__ methods
return sig.replace(parameters=tuple(sig.parameters.values())[1:])
if isinstance(obj, types.BuiltinFunctionType):
# Raise a nicer error message for builtins
msg = 'no signature found for builtin function {0!r}'.format(obj)
raise ValueError(msg)
raise ValueError('callable {0!r} is not supported by signature'.format(obj))
class _void(object):
'''A private marker - used in Parameter & Signature'''
class _empty(object):
pass
class _ParameterKind(int):
def __new__(self, *args, **kwargs):
obj = int.__new__(self, *args)
obj._name = kwargs['name']
return obj
def __str__(self):
return self._name
def __repr__(self):
return '<_ParameterKind: {0!r}>'.format(self._name)
_POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY')
_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
_VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL')
_KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY')
_VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD')
class Parameter(object):
'''Represents a parameter in a function signature.
Has the following public attributes:
* name : str
The name of the parameter as a string.
* default : object
The default value for the parameter if specified. If the
parameter has no default value, this attribute is not set.
* annotation
The annotation for the parameter if specified. If the
parameter has no annotation, this attribute is not set.
* kind : str
Describes how argument values are bound to the parameter.
Possible values: `Parameter.POSITIONAL_ONLY`,
`Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
'''
__slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
POSITIONAL_ONLY = _POSITIONAL_ONLY
POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
VAR_POSITIONAL = _VAR_POSITIONAL
KEYWORD_ONLY = _KEYWORD_ONLY
VAR_KEYWORD = _VAR_KEYWORD
empty = _empty
def __init__(self, name, kind, default=_empty, annotation=_empty,
_partial_kwarg=False):
if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
_VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
raise ValueError("invalid value for 'Parameter.kind' attribute")
self._kind = kind
if default is not _empty:
if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
msg = '{0} parameters cannot have default values'.format(kind)
raise ValueError(msg)
self._default = default
self._annotation = annotation
if name is None:
if kind != _POSITIONAL_ONLY:
raise ValueError("None is not a valid name for a "
"non-positional-only parameter")
self._name = name
else:
name = str(name)
if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
msg = '{0!r} is not a valid parameter name'.format(name)
raise ValueError(msg)
self._name = name
self._partial_kwarg = _partial_kwarg
@property
def name(self):
return self._name
@property
def default(self):
return self._default
@property
def annotation(self):
return self._annotation
@property
def kind(self):
return self._kind
def replace(self, name=_void, kind=_void, annotation=_void,
default=_void, _partial_kwarg=_void):
'''Creates a customized copy of the Parameter.'''
if name is _void:
name = self._name
if kind is _void:
kind = self._kind
if annotation is _void:
annotation = self._annotation
if default is _void:
default = self._default
if _partial_kwarg is _void:
_partial_kwarg = self._partial_kwarg
return type(self)(name, kind, default=default, annotation=annotation,
_partial_kwarg=_partial_kwarg)
def __str__(self):
kind = self.kind
formatted = self._name
if kind == _POSITIONAL_ONLY:
if formatted is None:
formatted = ''
formatted = '<{0}>'.format(formatted)
# Add annotation and default value
if self._annotation is not _empty:
formatted = '{0}:{1}'.format(formatted,
formatannotation(self._annotation))
if self._default is not _empty:
formatted = '{0}={1}'.format(formatted, repr(self._default))
if kind == _VAR_POSITIONAL:
formatted = '*' + formatted
elif kind == _VAR_KEYWORD:
formatted = '**' + formatted
return formatted
def __repr__(self):
return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
id(self), self.name)
def __hash__(self):
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
raise TypeError(msg)
def __eq__(self, other):
return (issubclass(other.__class__, Parameter) and
self._name == other._name and
self._kind == other._kind and
self._default == other._default and
self._annotation == other._annotation)
def __ne__(self, other):
return not self.__eq__(other)
class BoundArguments(object):
'''Result of `Signature.bind` call. Holds the mapping of arguments
to the function's parameters.
Has the following public attributes:
* arguments : OrderedDict
An ordered mutable mapping of parameters' names to arguments' values.
Does not contain arguments' default values.
* signature : Signature
The Signature object that created this instance.
* args : tuple
Tuple of positional arguments values.
* kwargs : dict
Dict of keyword arguments values.
'''
def __init__(self, signature, arguments):
self.arguments = arguments
self._signature = signature
@property
def signature(self):
return self._signature
@property
def args(self):
args = []
for param_name, param in self._signature.parameters.items():
if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
param._partial_kwarg):
# Keyword arguments mapped by 'functools.partial'
# (Parameter._partial_kwarg is True) are mapped
# in 'BoundArguments.kwargs', along with VAR_KEYWORD &
# KEYWORD_ONLY
break
try:
arg = self.arguments[param_name]
except KeyError:
# We're done here. Other arguments
# will be mapped in 'BoundArguments.kwargs'
break
else:
if param.kind == _VAR_POSITIONAL:
# *args
args.extend(arg)
else:
# plain argument
args.append(arg)
return tuple(args)
@property
def kwargs(self):
kwargs = {}
kwargs_started = False
for param_name, param in self._signature.parameters.items():
if not kwargs_started:
if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
param._partial_kwarg):
kwargs_started = True
else:
if param_name not in self.arguments:
kwargs_started = True
continue
if not kwargs_started:
continue
try:
arg = self.arguments[param_name]
except KeyError:
pass
else:
if param.kind == _VAR_KEYWORD:
# **kwargs
kwargs.update(arg)
else:
# plain keyword argument
kwargs[param_name] = arg
return kwargs
def __hash__(self):
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
raise TypeError(msg)
def __eq__(self, other):
return (issubclass(other.__class__, BoundArguments) and
self.signature == other.signature and
self.arguments == other.arguments)
def __ne__(self, other):
return not self.__eq__(other)
class Signature(object):
'''A Signature object represents the overall signature of a function.
It stores a Parameter object for each parameter accepted by the
function, as well as information specific to the function itself.
A Signature object has the following public attributes and methods:
* parameters : OrderedDict
An ordered mapping of parameters' names to the corresponding
Parameter objects (keyword-only arguments are in the same order
as listed in `code.co_varnames`).
* return_annotation : object
The annotation for the return type of the function if specified.
If the function has no annotation for its return type, this
attribute is not set.
* bind(*args, **kwargs) -> BoundArguments
Creates a mapping from positional and keyword arguments to
parameters.
* bind_partial(*args, **kwargs) -> BoundArguments
Creates a partial mapping from positional and keyword arguments
to parameters (simulating 'functools.partial' behavior.)
'''
__slots__ = ('_return_annotation', '_parameters')
_parameter_cls = Parameter
_bound_arguments_cls = BoundArguments
empty = _empty
def __init__(self, parameters=None, return_annotation=_empty,
__validate_parameters__=True):
'''Constructs Signature from the given list of Parameter
objects and 'return_annotation'. All arguments are optional.
'''
if parameters is None:
params = OrderedDict()
else:
if __validate_parameters__:
params = OrderedDict()
top_kind = _POSITIONAL_ONLY
for idx, param in enumerate(parameters):
kind = param.kind
if kind < top_kind:
msg = 'wrong parameter order: {0} before {1}'
msg = msg.format(top_kind, param.kind)
raise ValueError(msg)
else:
top_kind = kind
name = param.name
if name is None:
name = str(idx)
param = param.replace(name=name)
if name in params:
msg = 'duplicate parameter name: {0!r}'.format(name)
raise ValueError(msg)
params[name] = param
else:
params = OrderedDict(((param.name, param)
for param in parameters))
self._parameters = params
self._return_annotation = return_annotation
@classmethod
def from_function(cls, func):
'''Constructs Signature for the given python function'''
if not isinstance(func, types.FunctionType):
raise TypeError('{0!r} is not a Python function'.format(func))
Parameter = cls._parameter_cls
# Parameter information.
func_code = func.__code__
pos_count = func_code.co_argcount
arg_names = func_code.co_varnames
positional = tuple(arg_names[:pos_count])
keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
annotations = getattr(func, '__annotations__', {})
defaults = func.__defaults__
kwdefaults = getattr(func, '__kwdefaults__', None)
if defaults:
pos_default_count = len(defaults)
else:
pos_default_count = 0
parameters = []
# Non-keyword-only parameters w/o defaults.
non_default_count = pos_count - pos_default_count
for name in positional[:non_default_count]:
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_POSITIONAL_OR_KEYWORD))
# ... w/ defaults.
for offset, name in enumerate(positional[non_default_count:]):
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_POSITIONAL_OR_KEYWORD,
default=defaults[offset]))
# *args
if func_code.co_flags & 0x04:
name = arg_names[pos_count + keyword_only_count]
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_VAR_POSITIONAL))
# Keyword-only parameters.
for name in keyword_only:
default = _empty
if kwdefaults is not None:
default = kwdefaults.get(name, _empty)
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_KEYWORD_ONLY,
default=default))
# **kwargs
if func_code.co_flags & 0x08:
index = pos_count + keyword_only_count
if func_code.co_flags & 0x04:
index += 1
name = arg_names[index]
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_VAR_KEYWORD))
return cls(parameters,
return_annotation=annotations.get('return', _empty),
__validate_parameters__=False)
@property
def parameters(self):
try:
return types.MappingProxyType(self._parameters)
except AttributeError:
return OrderedDict(self._parameters.items())
@property
def return_annotation(self):
return self._return_annotation
def replace(self, parameters=_void, return_annotation=_void):
'''Creates a customized copy of the Signature.
Pass 'parameters' and/or 'return_annotation' arguments
to override them in the new copy.
'''
if parameters is _void:
parameters = self.parameters.values()
if return_annotation is _void:
return_annotation = self._return_annotation
return type(self)(parameters,
return_annotation=return_annotation)
def __hash__(self):
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
raise TypeError(msg)
def __eq__(self, other):
if (not issubclass(type(other), Signature) or
self.return_annotation != other.return_annotation or
len(self.parameters) != len(other.parameters)):
return False
other_positions = dict((param, idx)
for idx, param in enumerate(other.parameters.keys()))
for idx, (param_name, param) in enumerate(self.parameters.items()):
if param.kind == _KEYWORD_ONLY:
try:
other_param = other.parameters[param_name]
except KeyError:
return False
else:
if param != other_param:
return False
else:
try:
other_idx = other_positions[param_name]
except KeyError:
return False
else:
if (idx != other_idx or
param != other.parameters[param_name]):
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def _bind(self, args, kwargs, partial=False):
'''Private method. Don't use directly.'''
arguments = OrderedDict()
parameters = iter(self.parameters.values())
parameters_ex = ()
arg_vals = iter(args)
if partial:
# Support for binding arguments to 'functools.partial' objects.
# See 'functools.partial' case in 'signature()' implementation
# for details.
for param_name, param in self.parameters.items():
if (param._partial_kwarg and param_name not in kwargs):
# Simulating 'functools.partial' behavior
kwargs[param_name] = param.default
while True:
# Let's iterate through the positional arguments and corresponding
# parameters
try:
arg_val = next(arg_vals)
except StopIteration:
# No more positional arguments
try:
param = next(parameters)
except StopIteration:
# No more parameters. That's it. Just need to check that
# we have no `kwargs` after this while loop
break
else:
if param.kind == _VAR_POSITIONAL:
# That's OK, just empty *args. Let's start parsing
# kwargs
break
elif param.name in kwargs:
if param.kind == _POSITIONAL_ONLY:
msg = '{arg!r} parameter is positional only, ' \
'but was passed as a keyword'
msg = msg.format(arg=param.name)
raise TypeError(msg)
parameters_ex = (param,)
break
elif (param.kind == _VAR_KEYWORD or
param.default is not _empty):
# That's fine too - we have a default value for this
# parameter. So, lets start parsing `kwargs`, starting
# with the current parameter
parameters_ex = (param,)
break
else:
if partial:
parameters_ex = (param,)
break
else:
msg = '{arg!r} parameter lacking default value'
msg = msg.format(arg=param.name)
raise TypeError(msg)
else:
# We have a positional argument to process
try:
param = next(parameters)
except StopIteration:
raise TypeError('too many positional arguments')
else:
if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
# Looks like we have no parameter for this positional
# argument
raise TypeError('too many positional arguments')
if param.kind == _VAR_POSITIONAL:
# We have an '*args'-like argument, let's fill it with
# all positional arguments we have left and move on to
# the next phase
values = [arg_val]
values.extend(arg_vals)
arguments[param.name] = tuple(values)
break
if param.name in kwargs:
raise TypeError('multiple values for argument '
'{arg!r}'.format(arg=param.name))
arguments[param.name] = arg_val
# Now, we iterate through the remaining parameters to process
# keyword arguments
kwargs_param = None
for param in itertools.chain(parameters_ex, parameters):
if param.kind == _POSITIONAL_ONLY:
# This should never happen in case of a properly built
# Signature object (but let's have this check here
# to ensure correct behaviour just in case)
raise TypeError('{arg!r} parameter is positional only, '
'but was passed as a keyword'. \
format(arg=param.name))
if param.kind == _VAR_KEYWORD:
# Memorize that we have a '**kwargs'-like parameter
kwargs_param = param
continue
param_name = param.name
try:
arg_val = kwargs.pop(param_name)
except KeyError:
# We have no value for this parameter. It's fine though,
# if it has a default value, or it is an '*args'-like
# parameter, left alone by the processing of positional
# arguments.
if (not partial and param.kind != _VAR_POSITIONAL and
param.default is _empty):
raise TypeError('{arg!r} parameter lacking default value'. \
format(arg=param_name))
else:
arguments[param_name] = arg_val
if kwargs:
if kwargs_param is not None:
# Process our '**kwargs'-like parameter
arguments[kwargs_param.name] = kwargs
else:
raise TypeError('too many keyword arguments')
return self._bound_arguments_cls(self, arguments)
def bind(self, *args, **kwargs):
'''Get a BoundArguments object, that maps the passed `args`
and `kwargs` to the function's signature. Raises `TypeError`
if the passed arguments can not be bound.
'''
return self._bind(args, kwargs)
def bind_partial(self, *args, **kwargs):
'''Get a BoundArguments object, that partially maps the
passed `args` and `kwargs` to the function's signature.
Raises `TypeError` if the passed arguments can not be bound.
'''
return self._bind(args, kwargs, partial=True)
def __str__(self):
result = []
render_kw_only_separator = True
for idx, param in enumerate(self.parameters.values()):
formatted = str(param)
kind = param.kind
if kind == _VAR_POSITIONAL:
# OK, we have an '*args'-like parameter, so we won't need
# a '*' to separate keyword-only arguments
render_kw_only_separator = False
elif kind == _KEYWORD_ONLY and render_kw_only_separator:
# We have a keyword-only parameter to render and we haven't
# rendered an '*args'-like parameter before, so add a '*'
# separator to the parameters list ("foo(arg1, *, arg2)" case)
result.append('*')
# This condition should be only triggered once, so
# reset the flag
render_kw_only_separator = False
result.append(formatted)
rendered = '({0})'.format(', '.join(result))
if self.return_annotation is not _empty:
anno = formatannotation(self.return_annotation)
rendered += ' -> {0}'.format(anno)
return rendered

View File

@ -21,6 +21,7 @@ def test__is_dunder():
for name in non_dunder_names:
assert _is_dunder(name) is False
def test__is_sunder():
sunder_names = [
'_i_',

View File

@ -2,18 +2,13 @@ import re
from collections import Iterable, OrderedDict
from functools import partial
import six
from graphql_relay import connection_from_list
from promise import Promise, is_thenable
from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String,
Union)
from ..types import (Boolean, Enum, Int, Interface, List, NonNull, Scalar,
String, Union)
from ..types.field import Field
from ..types.objecttype import ObjectType, ObjectTypeMeta
from ..types.options import Options
from ..utils.is_base_type import is_base_type
from ..utils.props import props
from ..types.objecttype import ObjectType, ObjectTypeOptions
from .node import is_node
@ -41,56 +36,50 @@ class PageInfo(ObjectType):
)
class ConnectionMeta(ObjectTypeMeta):
class ConnectionOptions(ObjectTypeOptions):
node = None
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, ConnectionMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=None,
node=None,
)
options.interfaces = ()
options.local_fields = OrderedDict()
class Connection(ObjectType):
assert options.node, 'You have to provide a node in {}.Meta'.format(cls.__name__)
assert issubclass(options.node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), (
class Meta:
abstract = True
@classmethod
def __init_subclass_with_meta__(cls, node=None, name=None, **options):
_meta = ConnectionOptions(cls)
assert node, 'You have to provide a node in {}.Meta'.format(cls.__name__)
assert issubclass(node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), (
'Received incompatible node "{}" for Connection {}.'
).format(options.node, name)
).format(node, cls.__name__)
base_name = re.sub('Connection$', '', options.name) or options.node._meta.name
if not options.name:
options.name = '{}Connection'.format(base_name)
base_name = re.sub('Connection$', '', name or cls.__name__) or node._meta.name
if not name:
name = '{}Connection'.format(base_name)
edge_class = attrs.pop('Edge', None)
edge_class = getattr(cls, 'Edge', None)
_node = node
class EdgeBase(AbstractType):
node = Field(options.node, description='The item at the end of the edge')
class EdgeBase(object):
node = Field(_node, description='The item at the end of the edge')
cursor = String(required=True, description='A cursor for use in pagination')
edge_name = '{}Edge'.format(base_name)
if edge_class and issubclass(edge_class, AbstractType):
edge = type(edge_name, (EdgeBase, edge_class, ObjectType, ), {})
if edge_class:
edge_bases = (edge_class, EdgeBase, ObjectType,)
else:
edge_attrs = props(edge_class) if edge_class else {}
edge = type(edge_name, (EdgeBase, ObjectType, ), edge_attrs)
edge_bases = (EdgeBase, ObjectType,)
class ConnectionBase(AbstractType):
page_info = Field(PageInfo, name='pageInfo', required=True)
edges = NonNull(List(edge))
edge = type(edge_name, edge_bases, {})
cls.Edge = edge
bases = (ConnectionBase, ) + bases
attrs = dict(attrs, _meta=options, Edge=edge)
return ObjectTypeMeta.__new__(cls, name, bases, attrs)
class Connection(six.with_metaclass(ConnectionMeta, ObjectType)):
pass
_meta.name = name
_meta.node = node
_meta.fields = OrderedDict([
('page_info', Field(PageInfo, name='pageInfo', required=True)),
('edges', Field(NonNull(List(edge)))),
])
return super(Connection, cls).__init_subclass_with_meta__(_meta=_meta, **options)
class IterableConnectionField(Field):
@ -109,10 +98,13 @@ class IterableConnectionField(Field):
@property
def type(self):
type = super(IterableConnectionField, self).type
connection_type = type
if is_node(type):
connection_type = type.Connection
else:
connection_type = type
raise Exception(
"ConnectionField's now need a explicit ConnectionType for Nodes.\n"
"Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#node-connections"
)
assert issubclass(connection_type, Connection), (
'{} type have to be a subclass of Connection. Received "{}".'
).format(self.__class__.__name__, connection_type)
@ -138,8 +130,8 @@ class IterableConnectionField(Field):
return connection
@classmethod
def connection_resolver(cls, resolver, connection_type, root, args, context, info):
resolved = resolver(root, args, context, info)
def connection_resolver(cls, resolver, connection_type, root, info, **args):
resolved = resolver(root, info, **args)
on_resolve = partial(cls.resolve_connection, connection_type, args)
if is_thenable(resolved):

View File

@ -1,68 +1,72 @@
import re
from functools import partial
from collections import OrderedDict
import six
from promise import Promise, is_thenable
from promise import Promise
from ..types import Field, AbstractType, Argument, InputObjectType, String
from ..types.mutation import Mutation, MutationMeta
from ..types.objecttype import ObjectTypeMeta
from ..utils.is_base_type import is_base_type
from ..utils.props import props
from ..types import Field, InputObjectType, String
from ..types.mutation import Mutation
class ClientIDMutationMeta(MutationMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Mutation
if not is_base_type(bases, ClientIDMutationMeta):
return type.__new__(cls, name, bases, attrs)
class ClientIDMutation(Mutation):
input_class = attrs.pop('Input', None)
base_name = re.sub('Payload$', '', name)
if 'client_mutation_id' not in attrs:
attrs['client_mutation_id'] = String(name='clientMutationId')
cls = ObjectTypeMeta.__new__(cls, '{}Payload'.format(base_name), bases,
attrs)
class Meta:
abstract = True
@classmethod
def __init_subclass_with_meta__(cls, output=None, input_fields=None,
arguments=None, name=None, **options):
input_class = getattr(cls, 'Input', None)
base_name = re.sub('Payload$', '', name or cls.__name__)
assert not output, "Can't specify any output"
assert not arguments, "Can't specify any arguments"
bases = (InputObjectType, )
if input_class:
bases += (input_class, )
if not input_fields:
input_fields = {}
cls.Input = type(
'{}Input'.format(base_name),
bases,
OrderedDict(input_fields, client_mutation_id=String(
name='clientMutationId'))
)
arguments = OrderedDict(
input=cls.Input(required=True)
# 'client_mutation_id': String(name='clientMutationId')
)
mutate_and_get_payload = getattr(cls, 'mutate_and_get_payload', None)
if cls.mutate and cls.mutate.__func__ == ClientIDMutation.mutate.__func__:
assert mutate_and_get_payload, (
"{}.mutate_and_get_payload method is required"
" in a ClientIDMutation.").format(name)
input_attrs = {}
bases = ()
if not input_class:
input_attrs = {}
elif not issubclass(input_class, AbstractType):
input_attrs = props(input_class)
else:
bases += (input_class, )
input_attrs['client_mutation_id'] = String(name='clientMutationId')
cls.Input = type('{}Input'.format(base_name),
bases + (InputObjectType, ), input_attrs)
output_class = getattr(cls, 'Output', cls)
cls.Field = partial(
Field,
output_class,
resolver=cls.mutate,
input=Argument(cls.Input, required=True))
return cls
"{name}.mutate_and_get_payload method is required"
" in a ClientIDMutation.").format(name=name or cls.__name__)
if not name:
name = '{}Payload'.format(base_name)
super(ClientIDMutation, cls).__init_subclass_with_meta__(
output=None, arguments=arguments, name=name, **options)
cls._meta.fields['client_mutation_id'] = (
Field(String, name='clientMutationId')
)
class ClientIDMutation(six.with_metaclass(ClientIDMutationMeta, Mutation)):
@classmethod
def mutate(cls, root, args, context, info):
input = args.get('input')
def mutate(cls, root, info, input):
def on_resolve(payload):
try:
payload.client_mutation_id = input.get('clientMutationId')
payload.client_mutation_id = input.get('client_mutation_id')
except:
raise Exception(
('Cannot set client_mutation_id in the payload object {}'
).format(repr(payload)))
return payload
return Promise.resolve(
cls.mutate_and_get_payload(input, context, info)).then(on_resolve)
result = cls.mutate_and_get_payload(root, info, **input)
if is_thenable(result):
return Promise.resolve(result).then(on_resolve)
return on_resolve(result)

View File

@ -1,12 +1,11 @@
from collections import OrderedDict
from functools import partial
import six
from graphql_relay import from_global_id, to_global_id
from ..types import ID, Field, Interface, ObjectType
from ..types.interface import InterfaceOptions
from ..types.utils import get_type
from ..types.interface import InterfaceMeta
def is_node(objecttype):
@ -22,18 +21,6 @@ def is_node(objecttype):
return False
def get_default_connection(cls):
from .connection import Connection
assert issubclass(cls, ObjectType), (
'Can only get connection type on implemented Nodes.'
)
class Meta:
node = cls
return type('{}Connection'.format(cls.__name__), (Connection,), {'Meta': Meta})
class GlobalID(Field):
def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs):
@ -42,21 +29,15 @@ class GlobalID(Field):
self.parent_type_name = parent_type._meta.name if parent_type else None
@staticmethod
def id_resolver(parent_resolver, node, root, args, context, info, parent_type_name=None):
type_id = parent_resolver(root, args, context, info)
def id_resolver(parent_resolver, node, root, info, parent_type_name=None, **args):
type_id = parent_resolver(root, info, **args)
parent_type_name = parent_type_name or info.parent_type.name
return node.to_global_id(parent_type_name, type_id) # root._meta.name
def get_resolver(self, parent_resolver):
return partial(self.id_resolver, parent_resolver, self.node, parent_type_name=self.parent_type_name)
class NodeMeta(InterfaceMeta):
def __new__(cls, name, bases, attrs):
cls = InterfaceMeta.__new__(cls, name, bases, attrs)
cls._meta.fields['id'] = GlobalID(cls, description='The ID of the object.')
return cls
return partial(
self.id_resolver, parent_resolver, self.node, parent_type_name=self.parent_type_name
)
class NodeField(Field):
@ -75,10 +56,24 @@ class NodeField(Field):
)
def get_resolver(self, parent_resolver):
return partial(self.node_type.node_resolver, only_type=get_type(self.field_type))
return partial(self.node_type.node_resolver, get_type(self.field_type))
class Node(six.with_metaclass(NodeMeta, Interface)):
class AbstractNode(Interface):
class Meta:
abstract = True
@classmethod
def __init_subclass_with_meta__(cls, **options):
_meta = InterfaceOptions(cls)
_meta.fields = OrderedDict(
id=GlobalID(cls, description='The ID of the object.')
)
super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options)
class Node(AbstractNode):
'''An object with an ID'''
@classmethod
@ -86,11 +81,11 @@ class Node(six.with_metaclass(NodeMeta, Interface)):
return NodeField(cls, *args, **kwargs)
@classmethod
def node_resolver(cls, root, args, context, info, only_type=None):
return cls.get_node_from_global_id(args.get('id'), context, info, only_type)
def node_resolver(cls, only_type, root, info, id):
return cls.get_node_from_global_id(info, id, only_type=only_type)
@classmethod
def get_node_from_global_id(cls, global_id, context, info, only_type=None):
def get_node_from_global_id(cls, info, global_id, only_type=None):
try:
_type, _id = cls.from_global_id(global_id)
graphene_type = info.schema.get_type(_type).graphene_type
@ -108,7 +103,7 @@ class Node(six.with_metaclass(NodeMeta, Interface)):
get_node = getattr(graphene_type, 'get_node', None)
if get_node:
return get_node(_id, context, info)
return get_node(info, _id)
@classmethod
def from_global_id(cls, global_id):
@ -117,11 +112,3 @@ class Node(six.with_metaclass(NodeMeta, Interface)):
@classmethod
def to_global_id(cls, type, id):
return to_global_id(type, id)
@classmethod
def implements(cls, objecttype):
get_connection = getattr(objecttype, 'get_connection', None)
if not get_connection:
get_connection = partial(get_default_connection, objecttype)
objecttype.Connection = get_connection()

View File

@ -1,6 +1,7 @@
import pytest
from ...types import AbstractType, Field, List, NonNull, ObjectType, String, Argument, Int
from ..connection import Connection, PageInfo, ConnectionField
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String
from ..connection import Connection, ConnectionField, PageInfo
from ..node import Node
@ -38,7 +39,7 @@ def test_connection():
def test_connection_inherit_abstracttype():
class BaseConnection(AbstractType):
class BaseConnection(object):
extra = String()
class MyObjectConnection(BaseConnection, Connection):
@ -73,7 +74,7 @@ def test_edge():
def test_edge_with_bases():
class BaseEdge(AbstractType):
class BaseEdge(object):
extra = String()
class MyObjectConnection(Connection):
@ -96,16 +97,6 @@ def test_edge_with_bases():
assert edge_fields['other'].type == String
def test_edge_on_node():
Edge = MyObject.Connection.Edge
assert Edge._meta.name == 'MyObjectEdge'
edge_fields = Edge._meta.fields
assert list(edge_fields.keys()) == ['node', 'cursor']
assert isinstance(edge_fields['node'], Field)
assert edge_fields['node'].type == MyObject
def test_pageinfo():
assert PageInfo._meta.name == 'PageInfo'
fields = PageInfo._meta.fields
@ -114,6 +105,7 @@ def test_pageinfo():
def test_connectionfield():
class MyObjectConnection(Connection):
class Meta:
node = MyObject
@ -126,8 +118,16 @@ def test_connectionfield():
}
def test_connectionfield_node_deprecated():
field = ConnectionField(MyObject)
with pytest.raises(Exception) as exc_info:
field.type
assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str(exc_info.value)
def test_connectionfield_custom_args():
class MyObjectConnection(Connection):
class Meta:
node = MyObject

View File

@ -4,7 +4,7 @@ from graphql_relay.utils import base64
from promise import Promise
from ...types import ObjectType, Schema, String
from ..connection import ConnectionField, PageInfo
from ..connection import Connection, ConnectionField, PageInfo
from ..node import Node
letter_chars = ['A', 'B', 'C', 'D', 'E']
@ -18,27 +18,33 @@ class Letter(ObjectType):
letter = String()
class LetterConnection(Connection):
class Meta:
node = Letter
class Query(ObjectType):
letters = ConnectionField(Letter)
connection_letters = ConnectionField(Letter)
promise_letters = ConnectionField(Letter)
letters = ConnectionField(LetterConnection)
connection_letters = ConnectionField(LetterConnection)
promise_letters = ConnectionField(LetterConnection)
node = Node.Field()
def resolve_letters(self, args, context, info):
def resolve_letters(self, info, **args):
return list(letters.values())
def resolve_promise_letters(self, args, context, info):
def resolve_promise_letters(self, info, **args):
return Promise.resolve(list(letters.values()))
def resolve_connection_letters(self, args, context, info):
return Letter.Connection(
def resolve_connection_letters(self, info, **args):
return LetterConnection(
page_info=PageInfo(
has_next_page=True,
has_previous_page=False
),
edges=[
Letter.Connection.Edge(
LetterConnection.Edge(
node=Letter(id=0, letter='A'),
cursor='a-cursor'
),

View File

@ -1,8 +1,8 @@
from graphql_relay import to_global_id
from ..node import Node, GlobalID
from ...types import NonNull, ID, ObjectType, String
from ...types import ID, NonNull, ObjectType, String
from ...types.definitions import GrapheneObjectType
from ..node import GlobalID, Node
class CustomNode(Node):
@ -48,7 +48,7 @@ def test_global_id_defaults_to_info_parent_type():
my_id = '1'
gid = GlobalID()
id_resolver = gid.get_resolver(lambda *_: my_id)
my_global_id = id_resolver(None, None, None, Info(User))
my_global_id = id_resolver(None, Info(User))
assert my_global_id == to_global_id(User._meta.name, my_id)
@ -56,5 +56,5 @@ def test_global_id_allows_setting_customer_parent_type():
my_id = '1'
gid = GlobalID(parent_type=User)
id_resolver = gid.get_resolver(lambda *_: my_id)
my_global_id = id_resolver(None, None, None, None)
my_global_id = id_resolver(None, None)
assert my_global_id == to_global_id(User._meta.name, my_id)

View File

@ -1,62 +1,86 @@
import pytest
from ...types import (AbstractType, Argument, Field, InputField,
InputObjectType, NonNull, ObjectType, Schema)
from ...types.scalars import String
from ..mutation import ClientIDMutation
from ..node import Node
from promise import Promise
from ...types import (ID, Argument, Field, InputField, InputObjectType,
NonNull, ObjectType, Schema)
from ...types.scalars import String
from ..mutation import ClientIDMutation
class SharedFields(AbstractType):
class SharedFields(object):
shared = String()
class MyNode(ObjectType):
class Meta:
interfaces = (Node, )
# class Meta:
# interfaces = (Node, )
id = ID()
name = String()
class SaySomething(ClientIDMutation):
class Input:
what = String()
phrase = String()
@staticmethod
def mutate_and_get_payload(args, context, info):
what = args.get('what')
def mutate_and_get_payload(self, info, what, client_mutation_id=None):
return SaySomething(phrase=str(what))
class SaySomethingPromise(ClientIDMutation):
class FixedSaySomething(object):
__slots__ = 'phrase',
def __init__(self, phrase):
self.phrase = phrase
class SaySomethingFixed(ClientIDMutation):
class Input:
what = String()
phrase = String()
@staticmethod
def mutate_and_get_payload(args, context, info):
what = args.get('what')
def mutate_and_get_payload(self, info, what, client_mutation_id=None):
return FixedSaySomething(phrase=str(what))
class SaySomethingPromise(ClientIDMutation):
class Input:
what = String()
phrase = String()
@staticmethod
def mutate_and_get_payload(self, info, what, client_mutation_id=None):
return Promise.resolve(SaySomething(phrase=str(what)))
# MyEdge = MyNode.Connection.Edge
class MyEdge(ObjectType):
node = Field(MyNode)
cursor = String()
class OtherMutation(ClientIDMutation):
class Input(SharedFields):
additional_field = String()
name = String()
my_node_edge = Field(MyNode.Connection.Edge)
my_node_edge = Field(MyEdge)
@classmethod
def mutate_and_get_payload(cls, args, context, info):
shared = args.get('shared', '')
additionalField = args.get('additionalField', '')
edge_type = MyNode.Connection.Edge
@staticmethod
def mutate_and_get_payload(self, info, shared='', additional_field='', client_mutation_id=None):
edge_type = MyEdge
return OtherMutation(
name=shared + additionalField,
name=shared + additional_field,
my_node_edge=edge_type(cursor='1', node=MyNode(name='name')))
@ -66,6 +90,7 @@ class RootQuery(ObjectType):
class Mutation(ObjectType):
say = SaySomething.Field()
say_fixed = SaySomethingFixed.Field()
say_promise = SaySomethingPromise.Field()
other = OtherMutation.Field()
@ -86,6 +111,7 @@ def test_no_mutate_and_get_payload():
def test_mutation():
fields = SaySomething._meta.fields
assert list(fields.keys()) == ['phrase', 'client_mutation_id']
assert SaySomething._meta.name == "SaySomethingPayload"
assert isinstance(fields['phrase'], Field)
field = SaySomething.Field()
assert field.type == SaySomething
@ -146,7 +172,14 @@ def test_node_query():
assert executed.data == {'say': {'phrase': 'hello'}}
def test_node_query():
def test_node_query_fixed():
executed = schema.execute(
'mutation a { sayFixed(input: {what:"hello", clientMutationId:"1"}) { phrase } }'
)
assert "Cannot set client_mutation_id in the payload object" in str(executed.errors[0])
def test_node_query_promise():
executed = schema.execute(
'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }'
)

View File

@ -2,12 +2,11 @@ from collections import OrderedDict
from graphql_relay import to_global_id
from ...types import AbstractType, ObjectType, Schema, String
from ..connection import Connection
from ..node import Node
from ...types import ObjectType, Schema, String
from ..node import Node, is_node
class SharedNodeFields(AbstractType):
class SharedNodeFields(object):
shared = String()
something_else = String()
@ -23,7 +22,7 @@ class MyNode(ObjectType):
name = String()
@staticmethod
def get_node(id, *_):
def get_node(info, id):
return MyNode(name=str(id))
@ -37,7 +36,7 @@ class MyOtherNode(SharedNodeFields, ObjectType):
return 'extra field info.'
@staticmethod
def get_node(id, *_):
def get_node(info, id):
return MyOtherNode(shared=str(id))
@ -47,20 +46,14 @@ class RootQuery(ObjectType):
only_node = Node.Field(MyNode)
only_node_lazy = Node.Field(lambda: MyNode)
schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode])
def test_node_good():
assert 'id' in MyNode._meta.fields
def test_node_get_connection():
connection = MyNode.Connection
assert issubclass(connection, Connection)
def test_node_get_connection_dont_duplicate():
assert MyNode.Connection == MyNode.Connection
assert is_node(MyNode)
assert not is_node(object)
def test_node_query():
@ -80,6 +73,15 @@ def test_subclassed_node_query():
[('shared', '1'), ('extraField', 'extra field info.'), ('somethingElse', '----')])})
def test_node_requesting_non_node():
executed = schema.execute(
'{ node(id:"%s") { __typename } } ' % Node.to_global_id("RootQuery", 1)
)
assert executed.data == {
'node': None
}
def test_node_query_incorrect_id():
executed = schema.execute(
'{ node(id:"%s") { ... on MyNode { name } } }' % "something:2"
@ -114,7 +116,7 @@ def test_node_field_only_type_wrong():
)
assert len(executed.errors) == 1
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.'
assert executed.data == { 'onlyNode': None }
assert executed.data == {'onlyNode': None}
def test_node_field_only_lazy_type():
@ -131,7 +133,7 @@ def test_node_field_only_lazy_type_wrong():
)
assert len(executed.errors) == 1
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.'
assert executed.data == { 'onlyNodeLazy': None }
assert executed.data == {'onlyNodeLazy': None}
def test_str_schema():

View File

@ -15,7 +15,7 @@ class CustomNode(Node):
return id
@staticmethod
def get_node_from_global_id(id, context, info, only_type=None):
def get_node_from_global_id(info, id, only_type=None):
assert info.schema == schema
if id in user_data:
return user_data.get(id)

View File

@ -29,6 +29,7 @@ def format_execution_result(execution_result, format_error):
class Client(object):
def __init__(self, schema, format_error=None, **execute_options):
assert isinstance(schema, Schema)
self.schema = schema

View File

@ -3,9 +3,11 @@
import graphene
from graphene import resolve_only_args
class Query(graphene.ObjectType):
rand = graphene.String()
class Success(graphene.ObjectType):
yeah = graphene.String()
@ -15,18 +17,19 @@ class Error(graphene.ObjectType):
class CreatePostResult(graphene.Union):
class Meta:
types = [Success, Error]
class CreatePost(graphene.Mutation):
class Input:
text = graphene.String(required=True)
result = graphene.Field(CreatePostResult)
@resolve_only_args
def mutate(self, text):
def mutate(self, info, text):
result = Success(yeah='yeah')
return CreatePost(result=result)
@ -37,6 +40,7 @@ class Mutations(graphene.ObjectType):
# tests.py
def test_create_post():
query_string = '''
mutation {
@ -52,4 +56,4 @@ def test_create_post():
result = schema.execute(query_string)
assert not result.errors
assert result.data['createPost']['result']['__typename'] == 'Success'
assert result.data['createPost']['result']['__typename'] == 'Success'

View File

@ -1,19 +1,25 @@
# https://github.com/graphql-python/graphene/issues/356
import pytest
import graphene
from graphene import relay
class SomeTypeOne(graphene.ObjectType):
pass
class SomeTypeTwo(graphene.ObjectType):
pass
class MyUnion(graphene.Union):
class Meta:
types = (SomeTypeOne, SomeTypeTwo)
def test_issue():
with pytest.raises(Exception) as exc_info:
class Query(graphene.ObjectType):

View File

@ -1,36 +1,25 @@
# https://github.com/graphql-python/graphene/issues/425
import six
# Adapted for Graphene 2.0
from graphene.utils.is_base_type import is_base_type
from graphene.types.objecttype import ObjectTypeMeta, ObjectType
from graphene.types.options import Options
class SpecialObjectTypeMeta(ObjectTypeMeta):
@staticmethod
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# DjangoObjectType
if not is_base_type(bases, SpecialObjectTypeMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
other_attr='default',
)
cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options))
assert cls._meta is options
return cls
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
class SpecialObjectType(six.with_metaclass(SpecialObjectTypeMeta, ObjectType)):
pass
class SpecialOptions(ObjectTypeOptions):
other_attr = None
class SpecialObjectType(ObjectType):
@classmethod
def __init_subclass_with_meta__(cls, other_attr='default', **options):
_meta = SpecialOptions(cls)
_meta.other_attr = other_attr
super(SpecialObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
def test_special_objecttype_could_be_subclassed():
class MyType(SpecialObjectType):
class Meta:
other_attr = 'yeah!'
@ -49,5 +38,5 @@ def test_special_objecttype_inherit_meta_options():
pass
assert MyType._meta.name == 'MyType'
assert MyType._meta.default_resolver == None
assert MyType._meta.default_resolver is None
assert MyType._meta.interfaces == ()

View File

@ -1,14 +1,13 @@
# https://github.com/graphql-python/graphene/issues/313
import graphene
from graphene import resolve_only_args
class Query(graphene.ObjectType):
some_field = graphene.String(from_=graphene.String(name="from"))
def resolve_some_field(_, args, context, infos):
return args.get("from_")
def resolve_some_field(self, info, from_=None):
return from_
def test_issue():

View File

@ -1,10 +1,12 @@
# flake8: noqa
from graphql import ResolveInfo
from .objecttype import ObjectType
from .abstracttype import AbstractType
from .interface import Interface
from .mutation import Mutation
from .scalars import Scalar, String, ID, Int, Float, Boolean
from .json import JSONString
from .uuid import UUID
from .schema import Schema
from .structures import List, NonNull
from .enum import Enum
@ -14,10 +16,13 @@ from .argument import Argument
from .inputobjecttype import InputObjectType
from .dynamic import Dynamic
from .union import Union
from .context import Context
# Deprecated
from .abstracttype import AbstractType
__all__ = [
'AbstractType',
'ObjectType',
'InputObjectType',
'Interface',
@ -31,10 +36,17 @@ __all__ = [
'ID',
'Int',
'Float',
'JSONString',
'UUID',
'Boolean',
'List',
'NonNull',
'Argument',
'Dynamic',
'Union',
'Context',
'ResolveInfo',
# Deprecated
'AbstractType',
]

View File

@ -1,41 +1,12 @@
import six
from ..utils.is_base_type import is_base_type
from .options import Options
from .utils import get_base_fields, merge, yank_fields_from_attrs
from ..utils.subclass_with_meta import SubclassWithMeta
from ..utils.deprecated import warn_deprecation
class AbstractTypeMeta(type):
'''
AbstractType Definition
class AbstractType(SubclassWithMeta):
When we want to share fields across multiple types, like a Interface,
a ObjectType and a Input ObjectType we can use AbstractTypes for defining
our fields that the other types will inherit from.
'''
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# AbstractType
if not is_base_type(bases, AbstractTypeMeta):
return type.__new__(cls, name, bases, attrs)
for base in bases:
if not issubclass(base, AbstractType) and issubclass(type(base), AbstractTypeMeta):
# raise Exception('You can only extend AbstractTypes after the base definition.')
return type.__new__(cls, name, bases, attrs)
base_fields = get_base_fields(bases, _as=None)
fields = yank_fields_from_attrs(attrs, _as=None)
options = Options(
fields=merge(base_fields, fields)
def __init_subclass__(cls, *args, **kwargs):
warn_deprecation(
"Abstract type is deprecated, please use normal object inheritance instead.\n"
"See more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#deprecations"
)
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
return cls
class AbstractType(six.with_metaclass(AbstractTypeMeta)):
pass
super(AbstractType, cls).__init_subclass__(*args, **kwargs)

View File

@ -1,9 +1,9 @@
from collections import OrderedDict
from itertools import chain
from .dynamic import Dynamic
from .mountedtype import MountedType
from .structures import NonNull
from .dynamic import Dynamic
from .utils import get_type

42
graphene/types/base.py Normal file
View File

@ -0,0 +1,42 @@
from ..utils.subclass_with_meta import SubclassWithMeta
from ..utils.trim_docstring import trim_docstring
class BaseOptions(object):
name = None # type: str
description = None # type: str
_frozen = False # type: bool
def __init__(self, class_type):
self.class_type = class_type # type: Type
def freeze(self):
self._frozen = True
def __setattr__(self, name, value):
if not self._frozen:
super(BaseOptions, self).__setattr__(name, value)
else:
raise Exception("Can't modify frozen Options {0}".format(self))
def __repr__(self):
return "<{} type={}>".format(self.__class__.__name__, self.class_type.__name__)
class BaseType(SubclassWithMeta):
@classmethod
def create_type(cls, class_name, **options):
return type(class_name, (cls, ), {'Meta': options})
@classmethod
def __init_subclass_with_meta__(cls, name=None, description=None, _meta=None):
assert "_meta" not in cls.__dict__, "Can't assign directly meta"
if not _meta:
return
_meta.name = name or cls.__name__
_meta.description = description or trim_docstring(cls.__doc__)
_meta.freeze()
cls._meta = _meta
super(BaseType, cls).__init_subclass_with_meta__()

View File

@ -0,0 +1,4 @@
class Context(object):
def __init__(self, **params):
for key, value in params.items():
setattr(self, key, value)

View File

@ -2,15 +2,12 @@ from collections import OrderedDict
import six
from ..utils.is_base_type import is_base_type
from ..utils.trim_docstring import trim_docstring
from .options import Options
from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta
from .base import BaseOptions, BaseType
from .unmountedtype import UnmountedType
try:
from enum import Enum as PyEnum
except ImportError:
from ..pyutils.enum import Enum as PyEnum
from ..pyutils.compat import Enum as PyEnum
def eq_enum(self, other):
@ -19,29 +16,18 @@ def eq_enum(self, other):
return self.value is other
class EnumTypeMeta(type):
EnumType = type(PyEnum)
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, EnumTypeMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=trim_docstring(attrs.get('__doc__')),
enum=None,
)
if not options.enum:
attrs['__eq__'] = eq_enum
options.enum = PyEnum(cls.__name__, attrs)
class EnumOptions(BaseOptions):
enum = None # type: Enum
new_attrs = OrderedDict(attrs, _meta=options, **options.enum.__members__)
return type.__new__(cls, name, bases, new_attrs)
def __prepare__(name, bases, **kwargs): # noqa: N805
return OrderedDict()
class EnumMeta(SubclassWithMeta_Meta):
def __new__(cls, name, bases, classdict, **options):
enum = PyEnum(cls.__name__, OrderedDict(classdict, __eq__=eq_enum))
return SubclassWithMeta_Meta.__new__(cls, name, bases, OrderedDict(classdict, __enum__=enum), **options)
def get(cls, value):
return cls._meta.enum(value)
@ -49,29 +35,30 @@ class EnumTypeMeta(type):
def __getitem__(cls, value):
return cls._meta.enum[value]
def __prepare__(name, bases, **kwargs): # noqa: N805
return OrderedDict()
def __call__(cls, *args, **kwargs): # noqa: N805
if cls is Enum:
description = kwargs.pop('description', None)
return cls.from_enum(PyEnum(*args, **kwargs), description=description)
return super(EnumTypeMeta, cls).__call__(*args, **kwargs)
return super(EnumMeta, cls).__call__(*args, **kwargs)
# return cls._meta.enum(*args, **kwargs)
def from_enum(cls, enum, description=None): # noqa: N805
meta_class = type('Meta', (object,), {'enum': enum, 'description': description})
return type(meta_class.enum.__name__, (Enum,), {'Meta': meta_class})
def __str__(cls): # noqa: N805
return cls._meta.name
class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)):
class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)):
'''
Enum Type Definition
Some leaf values of requests and input values are Enums. GraphQL serializes
Enum values as strings, however internally Enums can be represented by any
kind of type, often integers.
'''
@classmethod
def __init_subclass_with_meta__(cls, enum=None, **options):
_meta = EnumOptions(cls)
_meta.enum = enum or cls.__enum__
for key, value in _meta.enum.__members__.items():
setattr(cls, key, value)
super(Enum, cls).__init_subclass_with_meta__(_meta=_meta, **options)
@classmethod
def get_type(cls):

View File

@ -8,11 +8,10 @@ from .structures import NonNull
from .unmountedtype import UnmountedType
from .utils import get_type
base_type = type
def source_resolver(source, root, args, context, info):
def source_resolver(source, root, info, **args):
resolved = getattr(root, source, None)
if inspect.isfunction(resolved) or inspect.ismethod(resolved):
return resolved()

View File

@ -1,9 +1,9 @@
from __future__ import unicode_literals
from graphene.types.scalars import MAX_INT, MIN_INT
from graphql.language.ast import (BooleanValue, FloatValue, IntValue,
StringValue, ListValue, ObjectValue)
ListValue, ObjectValue, StringValue)
from graphene.types.scalars import MIN_INT, MAX_INT
from .scalars import Scalar

View File

@ -1,45 +1,36 @@
import six
from collections import OrderedDict
from ..utils.is_base_type import is_base_type
from ..utils.trim_docstring import trim_docstring
from .abstracttype import AbstractTypeMeta
from .base import BaseOptions, BaseType
from .inputfield import InputField
from .options import Options
from .unmountedtype import UnmountedType
from .utils import get_base_fields, merge, yank_fields_from_attrs
from .utils import yank_fields_from_attrs
class InputObjectTypeMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# InputObjectType
if not is_base_type(bases, InputObjectTypeMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=trim_docstring(attrs.get('__doc__')),
local_fields=None,
)
options.base_fields = get_base_fields(bases, _as=InputField)
if not options.local_fields:
options.local_fields = yank_fields_from_attrs(attrs, _as=InputField)
options.fields = merge(
options.base_fields,
options.local_fields
)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls): # noqa: N802
return cls._meta.name
# For static type checking with Mypy
MYPY = False
if MYPY:
from typing import Dict, Callable # NOQA
class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)):
class InputObjectTypeOptions(BaseOptions):
fields = None # type: Dict[str, InputField]
create_container = None # type: Callable
class InputObjectTypeContainer(dict, BaseType):
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
for key, value in self.items():
setattr(self, key, value)
def __init_subclass__(cls, *args, **kwargs):
pass
class InputObjectType(UnmountedType, BaseType):
'''
Input Object Type Definition
@ -49,6 +40,22 @@ class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)):
Using `NonNull` will ensure that a value must be provided by the query
'''
@classmethod
def __init_subclass_with_meta__(cls, container=None, **options):
_meta = InputObjectTypeOptions(cls)
fields = OrderedDict()
for base in reversed(cls.__mro__):
fields.update(
yank_fields_from_attrs(base.__dict__, _as=InputField)
)
_meta.fields = fields
if container is None:
container = type(cls.__name__, (InputObjectTypeContainer, cls), {})
_meta.container = container
super(InputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
@classmethod
def get_type(cls):
'''

View File

@ -1,45 +1,20 @@
import six
from collections import OrderedDict
from ..utils.is_base_type import is_base_type
from ..utils.trim_docstring import trim_docstring
from .abstracttype import AbstractTypeMeta
from .base import BaseOptions, BaseType
from .field import Field
from .options import Options
from .utils import get_base_fields, merge, yank_fields_from_attrs
from .utils import yank_fields_from_attrs
# For static type checking with Mypy
MYPY = False
if MYPY:
from typing import Dict # NOQA
class InterfaceMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Interface
if not is_base_type(bases, InterfaceMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=trim_docstring(attrs.get('__doc__')),
local_fields=None,
)
options.base_fields = get_base_fields(bases, _as=Field)
if not options.local_fields:
options.local_fields = yank_fields_from_attrs(attrs, _as=Field)
options.fields = merge(
options.base_fields,
options.local_fields
)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls): # noqa: N802
return cls._meta.name
class InterfaceOptions(BaseOptions):
fields = None # type: Dict[str, Field]
class Interface(six.with_metaclass(InterfaceMeta)):
class Interface(BaseType):
'''
Interface Type Definition
@ -48,16 +23,29 @@ class Interface(six.with_metaclass(InterfaceMeta)):
all types, as well as a function to determine which type is actually used
when the field is resolved.
'''
@classmethod
def __init_subclass_with_meta__(cls, _meta=None, **options):
if not _meta:
_meta = InterfaceOptions(cls)
fields = OrderedDict()
for base in reversed(cls.__mro__):
fields.update(
yank_fields_from_attrs(base.__dict__, _as=Field)
)
if _meta.fields:
_meta.fields.update(fields)
else:
_meta.fields = fields
super(Interface, cls).__init_subclass_with_meta__(_meta=_meta, **options)
@classmethod
def resolve_type(cls, instance, context, info):
def resolve_type(cls, instance, info):
from .objecttype import ObjectType
if isinstance(instance, ObjectType):
return type(instance)
def __init__(self, *args, **kwargs):
raise Exception("An Interface cannot be intitialized")
@classmethod
def implements(cls, objecttype):
pass

View File

@ -1,33 +1,81 @@
from functools import partial
from collections import OrderedDict
import six
from ..utils.is_base_type import is_base_type
from ..utils.get_unbound_function import get_unbound_function
from ..utils.props import props
from .field import Field
from .objecttype import ObjectType, ObjectTypeMeta
from .objecttype import ObjectType, ObjectTypeOptions
from .utils import yank_fields_from_attrs
from ..utils.deprecated import warn_deprecation
class MutationMeta(ObjectTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Mutation
if not is_base_type(bases, MutationMeta):
return type.__new__(cls, name, bases, attrs)
input_class = attrs.pop('Input', None)
cls = ObjectTypeMeta.__new__(cls, name, bases, attrs)
field_args = props(input_class) if input_class else {}
output_class = getattr(cls, 'Output', cls)
resolver = getattr(cls, 'mutate', None)
assert resolver, 'All mutations must define a mutate method in it'
resolver = get_unbound_function(resolver)
cls.Field = partial(
Field, output_class, args=field_args, resolver=resolver)
return cls
# For static type checking with Mypy
MYPY = False
if MYPY:
from .argument import Argument # NOQA
from typing import Dict, Type, Callable # NOQA
class Mutation(six.with_metaclass(MutationMeta, ObjectType)):
pass
class MutationOptions(ObjectTypeOptions):
arguments = None # type: Dict[str, Argument]
output = None # type: Type[ObjectType]
resolver = None # type: Callable
class Mutation(ObjectType):
'''
Mutation Type Definition
'''
@classmethod
def __init_subclass_with_meta__(cls, resolver=None, output=None, arguments=None,
_meta=None, **options):
if not _meta:
_meta = MutationOptions(cls)
output = output or getattr(cls, 'Output', None)
fields = {}
if not output:
# If output is defined, we don't need to get the fields
fields = OrderedDict()
for base in reversed(cls.__mro__):
fields.update(
yank_fields_from_attrs(base.__dict__, _as=Field)
)
output = cls
if not arguments:
input_class = getattr(cls, 'Arguments', None)
if not input_class:
input_class = getattr(cls, 'Input', None)
if input_class:
warn_deprecation((
"Please use {name}.Arguments instead of {name}.Input."
"Input is now only used in ClientMutationID.\n"
"Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#mutation-input"
).format(name=cls.__name__))
if input_class:
arguments = props(input_class)
else:
arguments = {}
if not resolver:
mutate = getattr(cls, 'mutate', None)
assert mutate, 'All mutations must define a mutate method in it'
resolver = get_unbound_function(mutate)
if _meta.fields:
_meta.fields.update(fields)
else:
_meta.fields = fields
_meta.output = output
_meta.resolver = resolver
_meta.arguments = arguments
super(Mutation, cls).__init_subclass_with_meta__(_meta=_meta, **options)
@classmethod
def Field(cls, *args, **kwargs):
return Field(
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
)

View File

@ -1,82 +1,64 @@
from collections import OrderedDict
import six
from ..utils.is_base_type import is_base_type
from ..utils.trim_docstring import trim_docstring
from .abstracttype import AbstractTypeMeta
from .base import BaseOptions, BaseType
from .field import Field
from .interface import Interface
from .options import Options
from .utils import get_base_fields, merge, yank_fields_from_attrs
from .utils import yank_fields_from_attrs
# For static type checking with Mypy
MYPY = False
if MYPY:
from typing import Dict, Iterable, Type # NOQA
class ObjectTypeMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# ObjectType
if not is_base_type(bases, ObjectTypeMeta):
return type.__new__(cls, name, bases, attrs)
_meta = attrs.pop('_meta', None)
defaults = dict(
name=name,
description=trim_docstring(attrs.get('__doc__')),
interfaces=(),
possible_types=(),
default_resolver=None,
local_fields=OrderedDict(),
)
if not _meta:
options = Options(
attrs.pop('Meta', None),
**defaults
)
else:
options = _meta.extend_with_defaults(defaults)
options.base_fields = get_base_fields(bases, _as=Field)
if not options.local_fields:
options.local_fields = yank_fields_from_attrs(attrs=attrs, _as=Field)
options.interface_fields = OrderedDict()
for interface in options.interfaces:
assert issubclass(interface, Interface), (
'All interfaces of {} must be a subclass of Interface. Received "{}".'
).format(name, interface)
options.interface_fields.update(interface._meta.fields)
options.fields = merge(
options.interface_fields,
options.base_fields,
options.local_fields
)
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
assert not (options.possible_types and cls.is_type_of), (
'{}.Meta.possible_types will cause type collision with {}.is_type_of. '
'Please use one or other.'
).format(name, name)
for interface in options.interfaces:
interface.implements(cls)
return cls
def __str__(cls): # noqa: N802
return cls._meta.name
class ObjectTypeOptions(BaseOptions):
fields = None # type: Dict[str, Field]
interfaces = () # type: Iterable[Type[Interface]]
class ObjectType(six.with_metaclass(ObjectTypeMeta)):
class ObjectType(BaseType):
'''
Object Type Definition
Almost all of the GraphQL types you define will be object types. Object types
have a name, but most importantly describe their fields.
'''
@classmethod
def __init_subclass_with_meta__(
cls, interfaces=(),
possible_types=(),
default_resolver=None, _meta=None, **options):
if not _meta:
_meta = ObjectTypeOptions(cls)
fields = OrderedDict()
for interface in interfaces:
assert issubclass(interface, Interface), (
'All interfaces of {} must be a subclass of Interface. Received "{}".'
).format(cls.__name__, interface)
fields.update(interface._meta.fields)
for base in reversed(cls.__mro__):
fields.update(
yank_fields_from_attrs(base.__dict__, _as=Field)
)
assert not (possible_types and cls.is_type_of), (
'{name}.Meta.possible_types will cause type collision with {name}.is_type_of. '
'Please use one or other.'
).format(name=cls.__name__)
if _meta.fields:
_meta.fields.update(fields)
else:
_meta.fields = fields
_meta.interfaces = interfaces
_meta.possible_types = possible_types
_meta.default_resolver = default_resolver
super(ObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
is_type_of = None

View File

@ -1,42 +0,0 @@
import inspect
from ..utils.props import props
class Options(object):
'''
This is the class wrapper around Meta.
It helps to validate and cointain the attributes inside
'''
def __init__(self, meta=None, **defaults):
if meta:
assert inspect.isclass(meta), (
'Meta have to be a class, received "{}".'.format(repr(meta))
)
meta_attrs = props(meta) if meta else {}
for attr_name, value in defaults.items():
if attr_name in meta_attrs:
value = meta_attrs.pop(attr_name)
setattr(self, attr_name, value)
# If meta_attrs is not empty, it implicitly means
# it received invalid attributes
if meta_attrs:
raise TypeError(
"Invalid attributes: {}".format(
', '.join(sorted(meta_attrs.keys()))
)
)
def extend_with_defaults(self, defaults):
for attr_name, value in defaults.items():
if not hasattr(self, attr_name):
setattr(self, attr_name, value)
return self
def __repr__(self):
options_props = props(self)
props_as_attrs = ' '.join(['{}={}'.format(key, value) for key, value in options_props.items()])
return '<Options {}>'.format(props_as_attrs)

View File

@ -1,8 +1,8 @@
def attr_resolver(attname, default_value, root, args, context, info):
def attr_resolver(attname, default_value, root, info, **args):
return getattr(root, attname, default_value)
def dict_resolver(attname, default_value, root, args, context, info):
def dict_resolver(attname, default_value, root, info, **args):
return root.get(attname, default_value)

View File

@ -1,34 +1,17 @@
import six
from graphql.language.ast import (BooleanValue, FloatValue, IntValue,
StringValue)
from ..utils.is_base_type import is_base_type
from ..utils.trim_docstring import trim_docstring
from .options import Options
from .base import BaseOptions, BaseType
from .unmountedtype import UnmountedType
class ScalarTypeMeta(type):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, ScalarTypeMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=trim_docstring(attrs.get('__doc__')),
)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls): # noqa: N802
return cls._meta.name
class ScalarOptions(BaseOptions):
pass
class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
class Scalar(UnmountedType, BaseType):
'''
Scalar Type Definition
@ -36,6 +19,10 @@ class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
Scalars (or Enums) and are defined with a name and a series of functions
used to parse input from ast or variables and to ensure validity.
'''
@classmethod
def __init_subclass_with_meta__(cls, **options):
_meta = ScalarOptions(cls)
super(Scalar, cls).__init_subclass_with_meta__(_meta=_meta, **options)
serialize = None
parse_value = None
@ -99,6 +86,7 @@ class Float(Scalar):
@staticmethod
def coerce_float(value):
# type: (Any) -> float
try:
return float(value)
except ValueError:

View File

@ -68,9 +68,9 @@ class NonNull(Structure):
def __init__(self, *args, **kwargs):
super(NonNull, self).__init__(*args, **kwargs)
assert not isinstance(self.of_type, NonNull), (
assert not isinstance(self._of_type, NonNull), (
'Can only create NonNull of a Nullable GraphQLType but got: {}.'
).format(self.of_type)
).format(self._of_type)
def __str__(self):
return '{}!'.format(self.of_type)

View File

@ -1,10 +1,13 @@
import pytest
from ..abstracttype import AbstractType
from ..field import Field
from ..objecttype import ObjectType
from ..unmountedtype import UnmountedType
from ..abstracttype import AbstractType
from .. import abstracttype
from ..field import Field
class MyType(object):
class MyType(ObjectType):
pass
@ -14,29 +17,24 @@ class MyScalar(UnmountedType):
return MyType
def test_generate_abstracttype_with_fields():
def test_abstract_objecttype_warn_deprecation(mocker):
mocker.patch.object(abstracttype, 'warn_deprecation')
class MyAbstractType(AbstractType):
field = Field(MyType)
field1 = MyScalar()
assert 'field' in MyAbstractType._meta.fields
assert isinstance(MyAbstractType._meta.fields['field'], Field)
assert abstracttype.warn_deprecation.called
def test_generate_abstracttype_with_unmountedfields():
def test_generate_objecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
field = UnmountedType(MyType)
field1 = MyScalar()
assert 'field' in MyAbstractType._meta.fields
assert isinstance(MyAbstractType._meta.fields['field'], UnmountedType)
class MyObjectType(ObjectType, MyAbstractType):
field2 = MyScalar()
def test_generate_abstracttype_inheritance():
class MyAbstractType1(AbstractType):
field1 = UnmountedType(MyType)
class MyAbstractType2(MyAbstractType1):
field2 = UnmountedType(MyType)
assert list(MyAbstractType2._meta.fields.keys()) == ['field1', 'field2']
assert not hasattr(MyAbstractType1, 'field1')
assert not hasattr(MyAbstractType2, 'field2')
assert MyObjectType._meta.description is None
assert MyObjectType._meta.interfaces == ()
assert MyObjectType._meta.name == "MyObjectType"
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field]

View File

@ -1,11 +1,12 @@
import pytest
from functools import partial
import pytest
from ..argument import Argument, to_arguments
from ..field import Field
from ..inputfield import InputField
from ..structures import NonNull
from ..scalars import String
from ..structures import NonNull
def test_argument():
@ -73,4 +74,4 @@ def test_argument_with_lazy_type():
def test_argument_with_lazy_partial_type():
MyType = object()
arg = Argument(partial(lambda: MyType))
assert arg.type == MyType
assert arg.type == MyType

View File

@ -0,0 +1,63 @@
import pytest
from ..base import BaseType, BaseOptions
class CustomOptions(BaseOptions):
pass
class CustomType(BaseType):
@classmethod
def __init_subclass_with_meta__(cls, **options):
_meta = CustomOptions(cls)
super(CustomType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
def test_basetype():
class MyBaseType(CustomType):
pass
assert isinstance(MyBaseType._meta, CustomOptions)
assert MyBaseType._meta.name == "MyBaseType"
assert MyBaseType._meta.description is None
def test_basetype_nones():
class MyBaseType(CustomType):
'''Documentation'''
class Meta:
name = None
description = None
assert isinstance(MyBaseType._meta, CustomOptions)
assert MyBaseType._meta.name == "MyBaseType"
assert MyBaseType._meta.description == "Documentation"
def test_basetype_custom():
class MyBaseType(CustomType):
'''Documentation'''
class Meta:
name = 'Base'
description = 'Desc'
assert isinstance(MyBaseType._meta, CustomOptions)
assert MyBaseType._meta.name == "Base"
assert MyBaseType._meta.description == "Desc"
def test_basetype_create():
MyBaseType = CustomType.create_type('MyBaseType')
assert isinstance(MyBaseType._meta, CustomOptions)
assert MyBaseType._meta.name == "MyBaseType"
assert MyBaseType._meta.description is None
def test_basetype_create_extra():
MyBaseType = CustomType.create_type('MyBaseType', name='Base', description='Desc')
assert isinstance(MyBaseType._meta, CustomOptions)
assert MyBaseType._meta.name == "Base"
assert MyBaseType._meta.description == "Desc"

View File

@ -1,4 +1,5 @@
import datetime
import pytz
from ..datetime import DateTime, Time
@ -10,12 +11,11 @@ class Query(ObjectType):
datetime = DateTime(_in=DateTime(name='in'))
time = Time(_at=Time(name='at'))
def resolve_datetime(self, args, context, info):
_in = args.get('_in')
def resolve_datetime(self, info, _in=None):
return _in
def resolve_time(self, args, context, info):
return args.get('_at')
def resolve_time(self, info, _at=None):
return _at
schema = Schema(query=Query)

View File

@ -1,6 +1,5 @@
from ..abstracttype import AbstractType
from ..argument import Argument
from ..enum import Enum
from ..field import Field
@ -296,7 +295,7 @@ def test_stringifies_simple_types():
def test_does_not_mutate_passed_field_definitions():
class CommonFields(AbstractType):
class CommonFields(object):
field1 = String()
field2 = String(id=String())
@ -307,12 +306,8 @@ def test_does_not_mutate_passed_field_definitions():
pass
assert TestObject1._meta.fields == TestObject2._meta.fields
assert CommonFields._meta.fields == {
'field1': String(),
'field2': String(id=String()),
}
class CommonFields(AbstractType):
class CommonFields(object):
field1 = String()
field2 = String()
@ -323,8 +318,3 @@ def test_does_not_mutate_passed_field_definitions():
pass
assert TestInputObject1._meta.fields == TestInputObject2._meta.fields
assert CommonFields._meta.fields == {
'field1': String(),
'field2': String(),
}

View File

@ -1,6 +1,6 @@
from ..structures import List, NonNull
from ..scalars import String
from ..dynamic import Dynamic
from ..scalars import String
from ..structures import List, NonNull
def test_dynamic():

View File

@ -1,7 +1,7 @@
from ..argument import Argument
from ..enum import Enum, PyEnum
from ..field import Field
from ..inputfield import InputField
from ..argument import Argument
def test_enum_construction():

View File

@ -1,10 +1,11 @@
import pytest
from functools import partial
import pytest
from ..argument import Argument
from ..field import Field
from ..structures import NonNull
from ..scalars import String
from ..structures import NonNull
from .utils import MyLazyType
@ -19,10 +20,11 @@ class MyInstance(object):
def test_field_basic():
MyType = object()
args = {'my arg': Argument(True)}
resolver = lambda: None
def resolver(): return None
deprecation_reason = 'Deprecated now'
description = 'My Field'
my_default='something'
my_default = 'something'
field = Field(
MyType,
name='name',
@ -59,7 +61,7 @@ def test_field_default_value_not_callable():
def test_field_source():
MyType = object()
field = Field(MyType, source='value')
assert field.resolver(MyInstance, {}, None, None) == MyInstance.value
assert field.resolver(MyInstance(), None) == MyInstance.value
def test_field_with_lazy_type():
@ -83,19 +85,20 @@ def test_field_not_source_and_resolver():
MyType = object()
with pytest.raises(Exception) as exc_info:
Field(MyType, source='value', resolver=lambda: None)
assert str(exc_info.value) == 'A Field cannot have a source and a resolver in at the same time.'
assert str(
exc_info.value) == 'A Field cannot have a source and a resolver in at the same time.'
def test_field_source_func():
MyType = object()
field = Field(MyType, source='value_func')
assert field.resolver(MyInstance(), {}, None, None) == MyInstance.value_func()
assert field.resolver(MyInstance(), None) == MyInstance.value_func()
def test_field_source_method():
MyType = object()
field = Field(MyType, source='value_method')
assert field.resolver(MyInstance(), {}, None, None) == MyInstance().value_method()
assert field.resolver(MyInstance(), None) == MyInstance().value_method()
def test_field_source_as_argument():

View File

@ -6,8 +6,7 @@ from ..schema import Schema
class Query(ObjectType):
generic = GenericScalar(input=GenericScalar())
def resolve_generic(self, args, context, info):
input = args.get('input')
def resolve_generic(self, info, input=None):
return input

View File

@ -1,4 +1,3 @@
import pytest
from functools import partial
from ..inputfield import InputField

View File

@ -1,10 +1,9 @@
from ..abstracttype import AbstractType
from ..field import Field
from ..argument import Argument
from ..field import Field
from ..inputfield import InputField
from ..objecttype import ObjectType
from ..inputobjecttype import InputObjectType
from ..objecttype import ObjectType
from ..unmountedtype import UnmountedType
@ -69,7 +68,7 @@ def test_generate_inputobjecttype_as_argument():
class MyObjectType(ObjectType):
field = Field(MyType, input=MyInputObjectType())
assert 'field' in MyObjectType._meta.fields
field = MyObjectType._meta.fields['field']
assert isinstance(field, Field)
@ -80,7 +79,7 @@ def test_generate_inputobjecttype_as_argument():
def test_generate_inputobjecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
class MyAbstractType(object):
field1 = MyScalar(MyType)
class MyInputObjectType(InputObjectType, MyAbstractType):
@ -91,7 +90,7 @@ def test_generate_inputobjecttype_inherit_abstracttype():
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
class MyAbstractType(object):
field1 = MyScalar(MyType)
class MyInputObjectType(MyAbstractType, InputObjectType):

View File

@ -1,5 +1,3 @@
from ..abstracttype import AbstractType
from ..field import Field
from ..interface import Interface
from ..unmountedtype import UnmountedType
@ -61,7 +59,7 @@ def test_generate_interface_unmountedtype():
def test_generate_interface_inherit_abstracttype():
class MyAbstractType(AbstractType):
class MyAbstractType(object):
field1 = MyScalar()
class MyInterface(Interface, MyAbstractType):
@ -84,7 +82,7 @@ def test_generate_interface_inherit_interface():
def test_generate_interface_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
class MyAbstractType(object):
field1 = MyScalar()
class MyInterface(MyAbstractType, Interface):

View File

@ -1,4 +1,3 @@
import json
from ..json import JSONString
from ..objecttype import ObjectType
@ -8,8 +7,7 @@ from ..schema import Schema
class Query(ObjectType):
json = JSONString(input=JSONString())
def resolve_json(self, args, context, info):
input = args.get('input')
def resolve_json(self, info, input):
return input
schema = Schema(query=Query)
@ -19,7 +17,7 @@ def test_jsonstring_query():
json_value = '{"key": "value"}'
json_value_quoted = json_value.replace('"', '\\"')
result = schema.execute('''{ json(input: "%s") }'''%json_value_quoted)
result = schema.execute('''{ json(input: "%s") }''' % json_value_quoted)
assert not result.errors
assert result.data == {
'json': json_value

View File

@ -1,11 +1,10 @@
import pytest
from ..mountedtype import MountedType
from ..field import Field
from ..scalars import String
class CustomField(Field):
def __init__(self, *args, **kwargs):
self.metadata = kwargs.pop('metadata', None)
super(CustomField, self).__init__(*args, **kwargs)

View File

@ -1,40 +1,41 @@
import pytest
from ..argument import Argument
from ..dynamic import Dynamic
from ..mutation import Mutation
from ..objecttype import ObjectType
from ..schema import Schema
from ..argument import Argument
from ..scalars import String
from ..dynamic import Dynamic
from ..schema import Schema
def test_generate_mutation_no_args():
class MyMutation(Mutation):
'''Documentation'''
@classmethod
def mutate(cls, *args, **kwargs):
pass
def mutate(self, info, **args):
return args
assert issubclass(MyMutation, ObjectType)
assert MyMutation._meta.name == "MyMutation"
assert MyMutation._meta.description == "Documentation"
assert MyMutation.Field().resolver == MyMutation.mutate
resolved = MyMutation.Field().resolver(None, None, name='Peter')
assert resolved == {'name': 'Peter'}
def test_generate_mutation_with_meta():
class MyMutation(Mutation):
class Meta:
name = 'MyOtherMutation'
description = 'Documentation'
@classmethod
def mutate(cls, *args, **kwargs):
pass
def mutate(self, info, **args):
return args
assert MyMutation._meta.name == "MyOtherMutation"
assert MyMutation._meta.description == "Documentation"
assert MyMutation.Field().resolver == MyMutation.mutate
resolved = MyMutation.Field().resolver(None, None, name='Peter')
assert resolved == {'name': 'Peter'}
def test_mutation_raises_exception_if_no_mutate():
@ -52,24 +53,26 @@ def test_mutation_custom_output_type():
name = String()
class CreateUser(Mutation):
class Input:
name = String()
Output = User
@classmethod
def mutate(cls, args, context, info):
name = args.get('name')
def mutate(self, info, name):
return User(name=name)
field = CreateUser.Field()
assert field.type == User
assert field.args == {'name': Argument(String)}
assert field.resolver == CreateUser.mutate
resolved = field.resolver(None, None, name='Peter')
assert isinstance(resolved, User)
assert resolved.name == 'Peter'
def test_mutation_execution():
class CreateUser(Mutation):
class Input:
name = String()
dynamic = Dynamic(lambda: String())
@ -78,9 +81,7 @@ def test_mutation_execution():
name = String()
dynamic = Dynamic(lambda: String())
def mutate(self, args, context, info):
name = args.get('name')
dynamic = args.get('dynamic')
def mutate(self, info, name, dynamic):
return CreateUser(name=name, dynamic=dynamic)
class Query(ObjectType):

View File

@ -1,6 +1,5 @@
import pytest
from ..abstracttype import AbstractType
from ..field import Field
from ..interface import Interface
from ..objecttype import ObjectType
@ -89,7 +88,7 @@ def test_ordered_fields_in_objecttype():
def test_generate_objecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
class MyAbstractType(object):
field1 = MyScalar()
class MyObjectType(ObjectType, MyAbstractType):
@ -103,7 +102,7 @@ def test_generate_objecttype_inherit_abstracttype():
def test_generate_objecttype_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
class MyAbstractType(object):
field1 = MyScalar()
class MyObjectType(MyAbstractType, ObjectType):
@ -188,6 +187,7 @@ def test_generate_objecttype_description():
def test_objecttype_with_possible_types():
class MyObjectType(ObjectType):
class Meta:
possible_types = (dict, )
@ -197,6 +197,7 @@ def test_objecttype_with_possible_types():
def test_objecttype_with_possible_types_and_is_type_of_should_raise():
with pytest.raises(AssertionError) as excinfo:
class MyObjectType(ObjectType):
class Meta:
possible_types = (dict, )

View File

@ -1,30 +0,0 @@
import pytest
from ..options import Options
def test_options():
class BaseOptions:
option_1 = False
name = True
meta = Options(BaseOptions, name=False, option_1=False)
assert meta.name == True
assert meta.option_1 == False
def test_options_extra_attrs():
class BaseOptions:
name = True
type = True
with pytest.raises(Exception) as exc_info:
meta = Options(BaseOptions)
assert str(exc_info.value) == 'Invalid attributes: name, type'
def test_options_repr():
class BaseOptions:
name = True
meta = Options(BaseOptions, name=False)
assert repr(meta) == '<Options name=True>'

View File

@ -1,18 +1,19 @@
import json
from functools import partial
from graphql import Source, execute, parse, GraphQLError
from graphql import GraphQLError, Source, execute, parse, ResolveInfo
from ..dynamic import Dynamic
from ..field import Field
from ..interface import Interface
from ..inputfield import InputField
from ..inputobjecttype import InputObjectType
from ..interface import Interface
from ..objecttype import ObjectType
from ..scalars import Int, String
from ..schema import Schema
from ..structures import List
from ..union import Union
from ..dynamic import Dynamic
from ..context import Context
def test_query():
@ -26,6 +27,23 @@ def test_query():
assert executed.data == {'hello': 'World'}
def test_query_source():
class Root(object):
_hello = "World"
def hello(self):
return self._hello
class Query(ObjectType):
hello = String(source="hello")
hello_schema = Schema(Query)
executed = hello_schema.execute('{ hello }', Root())
assert not executed.errors
assert executed.data == {'hello': 'World'}
def test_query_union():
class one_object(object):
pass
@ -37,24 +55,25 @@ def test_query_union():
one = String()
@classmethod
def is_type_of(cls, root, context, info):
def is_type_of(cls, root, info):
return isinstance(root, one_object)
class Two(ObjectType):
two = String()
@classmethod
def is_type_of(cls, root, context, info):
def is_type_of(cls, root, info):
return isinstance(root, two_object)
class MyUnion(Union):
class Meta:
types = (One, Two)
class Query(ObjectType):
unions = List(MyUnion)
def resolve_unions(self, args, context, info):
def resolve_unions(self, info):
return [one_object(), two_object()]
hello_schema = Schema(Query)
@ -81,29 +100,31 @@ def test_query_interface():
base = String()
class One(ObjectType):
class Meta:
interfaces = (MyInterface, )
one = String()
@classmethod
def is_type_of(cls, root, context, info):
def is_type_of(cls, root, info):
return isinstance(root, one_object)
class Two(ObjectType):
class Meta:
interfaces = (MyInterface, )
two = String()
@classmethod
def is_type_of(cls, root, context, info):
def is_type_of(cls, root, info):
return isinstance(root, two_object)
class Query(ObjectType):
interfaces = List(MyInterface)
def resolve_interfaces(self, args, context, info):
def resolve_interfaces(self, info):
return [one_object(), two_object()]
hello_schema = Schema(Query, types=[One, Two])
@ -123,13 +144,15 @@ def test_query_dynamic():
class Query(ObjectType):
hello = Dynamic(lambda: String(resolver=lambda *_: 'World'))
hellos = Dynamic(lambda: List(String, resolver=lambda *_: ['Worlds']))
hello_field = Dynamic(lambda: Field(String, resolver=lambda *_: 'Field World'))
hello_field = Dynamic(lambda: Field(
String, resolver=lambda *_: 'Field World'))
hello_schema = Schema(Query)
executed = hello_schema.execute('{ hello hellos helloField }')
assert not executed.errors
assert executed.data == {'hello': 'World', 'hellos': ['Worlds'], 'helloField': 'Field World'}
assert executed.data == {'hello': 'World', 'hellos': [
'Worlds'], 'helloField': 'Field World'}
def test_query_default_value():
@ -151,7 +174,7 @@ def test_query_wrong_default_value():
field = String()
@classmethod
def is_type_of(cls, root, context, info):
def is_type_of(cls, root, info):
return isinstance(root, MyType)
class Query(ObjectType):
@ -161,7 +184,8 @@ def test_query_wrong_default_value():
executed = hello_schema.execute('{ hello { field } }')
assert len(executed.errors) == 1
assert executed.errors[0].message == GraphQLError('Expected value of type "MyType" but got: str.').message
assert executed.errors[0].message == GraphQLError(
'Expected value of type "MyType" but got: str.').message
assert executed.data == {'hello': None}
@ -170,7 +194,8 @@ def test_query_default_value_ignored_by_resolver():
field = String()
class Query(ObjectType):
hello = Field(MyType, default_value='hello', resolver=lambda *_: MyType(field='no default.'))
hello = Field(MyType, default_value='hello',
resolver=lambda *_: MyType(field='no default.'))
hello_schema = Schema(Query)
@ -183,7 +208,7 @@ def test_query_resolve_function():
class Query(ObjectType):
hello = String()
def resolve_hello(self, args, context, info):
def resolve_hello(self, info):
return 'World'
hello_schema = Schema(Query)
@ -197,7 +222,7 @@ def test_query_arguments():
class Query(ObjectType):
test = String(a_str=String(), a_int=Int())
def resolve_test(self, args, context, info):
def resolve_test(self, info, **args):
return json.dumps([self, args], separators=(',', ':'))
test_schema = Schema(Query)
@ -210,7 +235,8 @@ def test_query_arguments():
assert not result.errors
assert result.data == {'test': '["Source!",{"a_str":"String!"}]'}
result = test_schema.execute('{ test(aInt: -123, aStr: "String!") }', 'Source!')
result = test_schema.execute(
'{ test(aInt: -123, aStr: "String!") }', 'Source!')
assert not result.errors
assert result.data in [
{'test': '["Source!",{"a_str":"String!","a_int":-123}]'},
@ -226,7 +252,7 @@ def test_query_input_field():
class Query(ObjectType):
test = String(a_input=Input())
def resolve_test(self, args, context, info):
def resolve_test(self, info, **args):
return json.dumps([self, args], separators=(',', ':'))
test_schema = Schema(Query)
@ -235,13 +261,17 @@ def test_query_input_field():
assert not result.errors
assert result.data == {'test': '[null,{}]'}
result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', 'Source!')
result = test_schema.execute(
'{ test(aInput: {aField: "String!"} ) }', 'Source!')
assert not result.errors
assert result.data == {'test': '["Source!",{"a_input":{"a_field":"String!"}}]'}
assert result.data == {
'test': '["Source!",{"a_input":{"a_field":"String!"}}]'}
result = test_schema.execute('{ test(aInput: {recursiveField: {aField: "String!"}}) }', 'Source!')
result = test_schema.execute(
'{ test(aInput: {recursiveField: {aField: "String!"}}) }', 'Source!')
assert not result.errors
assert result.data == {'test': '["Source!",{"a_input":{"recursive_field":{"a_field":"String!"}}}]'}
assert result.data == {
'test': '["Source!",{"a_input":{"recursive_field":{"a_field":"String!"}}}]'}
def test_query_middlewares():
@ -249,10 +279,10 @@ def test_query_middlewares():
hello = String()
other = String()
def resolve_hello(self, args, context, info):
def resolve_hello(self, info):
return 'World'
def resolve_other(self, args, context, info):
def resolve_other(self, info):
return 'other'
def reversed_middleware(next, *args, **kwargs):
@ -261,27 +291,29 @@ def test_query_middlewares():
hello_schema = Schema(Query)
executed = hello_schema.execute('{ hello, other }', middleware=[reversed_middleware])
executed = hello_schema.execute(
'{ hello, other }', middleware=[reversed_middleware])
assert not executed.errors
assert executed.data == {'hello': 'dlroW', 'other': 'rehto'}
def test_objecttype_on_instances():
class Ship:
def __init__(self, name):
self.name = name
class ShipType(ObjectType):
name = String(description="Ship name", required=True)
def resolve_name(self, context, args, info):
def resolve_name(self, info):
# Here self will be the Ship instance returned in resolve_ship
return self.name
class Query(ObjectType):
ship = Field(ShipType)
def resolve_ship(self, context, args, info):
def resolve_ship(self, info):
return Ship(name='xwing')
schema = Schema(query=Query)
@ -296,7 +328,7 @@ def test_big_list_query_benchmark(benchmark):
class Query(ObjectType):
all_ints = List(Int)
def resolve_all_ints(self, args, context, info):
def resolve_all_ints(self, info):
return big_list
hello_schema = Schema(Query)
@ -313,7 +345,7 @@ def test_big_list_query_compiled_query_benchmark(benchmark):
class Query(ObjectType):
all_ints = List(Int)
def resolve_all_ints(self, args, context, info):
def resolve_all_ints(self, info):
return big_list
hello_schema = Schema(Query)
@ -335,7 +367,7 @@ def test_big_list_of_containers_query_benchmark(benchmark):
class Query(ObjectType):
all_containers = List(Container)
def resolve_all_containers(self, args, context, info):
def resolve_all_containers(self, info):
return big_container_list
hello_schema = Schema(Query)
@ -343,7 +375,8 @@ def test_big_list_of_containers_query_benchmark(benchmark):
big_list_query = partial(hello_schema.execute, '{ allContainers { x } }')
result = benchmark(big_list_query)
assert not result.errors
assert result.data == {'allContainers': [{'x': c.x} for c in big_container_list]}
assert result.data == {'allContainers': [
{'x': c.x} for c in big_container_list]}
def test_big_list_of_containers_multiple_fields_query_benchmark(benchmark):
@ -358,15 +391,17 @@ def test_big_list_of_containers_multiple_fields_query_benchmark(benchmark):
class Query(ObjectType):
all_containers = List(Container)
def resolve_all_containers(self, args, context, info):
def resolve_all_containers(self, info):
return big_container_list
hello_schema = Schema(Query)
big_list_query = partial(hello_schema.execute, '{ allContainers { x, y, z, o } }')
big_list_query = partial(hello_schema.execute,
'{ allContainers { x, y, z, o } }')
result = benchmark(big_list_query)
assert not result.errors
assert result.data == {'allContainers': [{'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]}
assert result.data == {'allContainers': [
{'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]}
def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark(benchmark):
@ -376,16 +411,16 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark
z = Int()
o = Int()
def resolve_x(self, args, context, info):
def resolve_x(self, info):
return self.x
def resolve_y(self, args, context, info):
def resolve_y(self, info):
return self.y
def resolve_z(self, args, context, info):
def resolve_z(self, info):
return self.z
def resolve_o(self, args, context, info):
def resolve_o(self, info):
return self.o
big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(1000)]
@ -393,12 +428,50 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark
class Query(ObjectType):
all_containers = List(Container)
def resolve_all_containers(self, args, context, info):
def resolve_all_containers(self, info):
return big_container_list
hello_schema = Schema(Query)
big_list_query = partial(hello_schema.execute, '{ allContainers { x, y, z, o } }')
big_list_query = partial(hello_schema.execute,
'{ allContainers { x, y, z, o } }')
result = benchmark(big_list_query)
assert not result.errors
assert result.data == {'allContainers': [{'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]}
assert result.data == {'allContainers': [
{'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]}
def test_query_annotated_resolvers():
import json
context = Context(key="context")
class Query(ObjectType):
annotated = String(id=String())
context = String()
info = String()
def resolve_annotated(self, info, id):
return "{}-{}".format(self, id)
def resolve_context(self, info):
assert isinstance(info.context, Context)
return "{}-{}".format(self, info.context.key)
def resolve_info(self, info):
assert isinstance(info, ResolveInfo)
return "{}-{}".format(self, info.field_name)
test_schema = Schema(Query)
result = test_schema.execute('{ annotated(id:"self") }', "base")
assert not result.errors
assert result.data == {'annotated': 'base-self'}
result = test_schema.execute('{ context }', "base", context_value=context)
assert not result.errors
assert result.data == {'context': 'base-context'}
result = test_schema.execute('{ info }', "base")
assert not result.errors
assert result.data == {'info': 'base-info'}

View File

@ -1,6 +1,6 @@
import pytest
from ..resolver import attr_resolver, dict_resolver, get_default_resolver, set_default_resolver
from ..resolver import (attr_resolver, dict_resolver, get_default_resolver,
set_default_resolver)
args = {}
context = None
@ -16,22 +16,22 @@ class demo_obj(object):
def test_attr_resolver():
resolved = attr_resolver('attr', None, demo_obj, args, context, info)
resolved = attr_resolver('attr', None, demo_obj, info, **args)
assert resolved == 'value'
def test_attr_resolver_default_value():
resolved = attr_resolver('attr2', 'default', demo_obj, args, context, info)
resolved = attr_resolver('attr2', 'default', demo_obj, info, **args)
assert resolved == 'default'
def test_dict_resolver():
resolved = dict_resolver('attr', None, demo_dict, args, context, info)
resolved = dict_resolver('attr', None, demo_dict, info, **args)
assert resolved == 'value'
def test_dict_resolver_default_value():
resolved = dict_resolver('attr2', 'default', demo_dict, args, context, info)
resolved = dict_resolver('attr2', 'default', demo_dict, info, **args)
assert resolved == 'default'

View File

@ -0,0 +1,10 @@
from ..scalars import Scalar
def test_scalar():
class JSONScalar(Scalar):
'''Documentation'''
assert JSONScalar._meta.name == "JSONScalar"
assert JSONScalar._meta.description == "Documentation"

View File

@ -1,9 +1,9 @@
import pytest
from ..schema import Schema
from ..field import Field
from ..objecttype import ObjectType
from ..scalars import String
from ..field import Field
from ..schema import Schema
class MyOtherType(ObjectType):

View File

@ -1,8 +1,9 @@
import pytest
from functools import partial
from ..structures import List, NonNull
import pytest
from ..scalars import String
from ..structures import List, NonNull
from .utils import MyLazyType
@ -15,7 +16,7 @@ def test_list():
def test_list_with_unmounted_type():
with pytest.raises(Exception) as exc_info:
List(String())
assert str(exc_info.value) == 'List could not have a mounted String() as inner type. Try with List(String).'
@ -80,14 +81,14 @@ def test_nonnull_inherited_works_list():
def test_nonnull_inherited_dont_work_nonnull():
with pytest.raises(Exception) as exc_info:
NonNull(NonNull(String))
assert str(exc_info.value) == 'Can only create NonNull of a Nullable GraphQLType but got: String!.'
def test_nonnull_with_unmounted_type():
with pytest.raises(Exception) as exc_info:
NonNull(String())
assert str(exc_info.value) == 'NonNull could not have a mounted String() as inner type. Try with NonNull(String).'

View File

@ -49,8 +49,8 @@ def test_objecttype():
foo = String(bar=String(description='Argument description', default_value='x'), description='Field description')
bar = String(name='gizmo')
def resolve_foo(self, args, info):
return args.get('bar')
def resolve_foo(self, bar):
return bar
typemap = TypeMap([MyObjectType])
assert 'MyObjectType' in typemap
@ -65,7 +65,7 @@ def test_objecttype():
assert isinstance(foo_field, GraphQLField)
assert foo_field.description == 'Field description'
f = MyObjectType.resolve_foo
assert foo_field.resolver == getattr(f, '__func__', f)
# assert foo_field.resolver == getattr(f, '__func__', f)
assert foo_field.args == {
'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar')
}
@ -135,9 +135,17 @@ def test_inputobject():
assert graphql_type.name == 'MyInputObjectType'
assert graphql_type.description == 'Description'
# Container
container = graphql_type.create_container({'bar': 'oh!'})
assert isinstance(container, MyInputObjectType)
assert 'bar' in container
assert container.bar == 'oh!'
assert 'foo_bar' not in container
fields = graphql_type.fields
assert list(fields.keys()) == ['fooBar', 'gizmo', 'own']
assert fields['own'].type == graphql_type
own_field = fields['own']
assert own_field.type == graphql_type
foo_field = fields['fooBar']
assert isinstance(foo_field, GraphQLInputObjectField)
assert foo_field.description == 'Field description'
@ -196,5 +204,5 @@ def test_objecttype_with_possible_types():
typemap = TypeMap([MyObjectType])
graphql_type = typemap['MyObjectType']
assert graphql_type.is_type_of
assert graphql_type.is_type_of({}, None, None) is True
assert graphql_type.is_type_of(MyObjectType(), None, None) is False
assert graphql_type.is_type_of({}, None) is True
assert graphql_type.is_type_of(MyObjectType(), None) is False

View File

@ -47,6 +47,7 @@ def test_generate_union_with_no_types():
def test_union_can_be_mounted():
class MyUnion(Union):
class Meta:
types = (MyObjectType1, MyObjectType2)

View File

@ -0,0 +1,34 @@
from ..uuid import UUID
from ..objecttype import ObjectType
from ..schema import Schema
class Query(ObjectType):
uuid = UUID(input=UUID())
def resolve_uuid(self, info, input):
return input
schema = Schema(query=Query)
def test_uuidstring_query():
uuid_value = 'dfeb3bcf-70fd-11e7-a61a-6003088f8204'
result = schema.execute('''{ uuid(input: "%s") }''' % uuid_value)
assert not result.errors
assert result.data == {
'uuid': uuid_value
}
def test_uuidstring_query_variable():
uuid_value = 'dfeb3bcf-70fd-11e7-a61a-6003088f8204'
result = schema.execute(
'''query Test($uuid: UUID){ uuid(input: $uuid) }''',
variable_values={'uuid': uuid_value}
)
assert not result.errors
assert result.data == {
'uuid': uuid_value
}

View File

@ -11,10 +11,10 @@ from graphql.type.typemap import GraphQLTypeMap
from ..utils.get_unbound_function import get_unbound_function
from ..utils.str_converters import to_camel_case
from .definitions import (GrapheneEnumType, GrapheneInputObjectType,
GrapheneInterfaceType, GrapheneObjectType,
GrapheneScalarType, GrapheneUnionType,
GrapheneGraphQLType)
from .definitions import (GrapheneEnumType, GrapheneGraphQLType,
GrapheneInputObjectType, GrapheneInterfaceType,
GrapheneObjectType, GrapheneScalarType,
GrapheneUnionType)
from .dynamic import Dynamic
from .enum import Enum
from .field import Field
@ -37,12 +37,12 @@ def is_graphene_type(_type):
return True
def resolve_type(resolve_type_func, map, type_name, root, context, info):
_type = resolve_type_func(root, context, info)
def resolve_type(resolve_type_func, map, type_name, root, info):
_type = resolve_type_func(root, info)
if not _type:
return_type = map[type_name]
return get_default_resolve_type_fn(root, context, info, return_type)
return get_default_resolve_type_fn(root, info, return_type)
if inspect.isclass(_type) and issubclass(_type, ObjectType):
graphql_type = map.get(_type._meta.name)
@ -54,11 +54,12 @@ def resolve_type(resolve_type_func, map, type_name, root, context, info):
return _type
def is_type_of_from_possible_types(possible_types, root, context, info):
def is_type_of_from_possible_types(possible_types, root, info):
return isinstance(root, possible_types)
class TypeMap(GraphQLTypeMap):
def __init__(self, types, auto_camelcase=True, schema=None):
self.auto_camelcase = auto_camelcase
self.schema = schema
@ -194,6 +195,7 @@ class TypeMap(GraphQLTypeMap):
graphene_type=type,
name=type._meta.name,
description=type._meta.description,
container_type=type._meta.container,
fields=partial(
self.construct_fields_for_type, map, type, is_input_type=True),
)
@ -237,7 +239,7 @@ class TypeMap(GraphQLTypeMap):
_field = GraphQLInputObjectField(
field_type,
default_value=field.default_value,
out_name=field.name or name,
out_name=name,
description=field.description)
else:
args = OrderedDict()
@ -254,8 +256,12 @@ class TypeMap(GraphQLTypeMap):
field_type,
args=args,
resolver=field.get_resolver(
self.get_resolver_for_type(type, name,
field.default_value)),
self.get_resolver_for_type(
type,
name,
field.default_value
)
),
deprecation_reason=field.deprecation_reason,
description=field.description)
field_name = field.name or self.get_name(name)

View File

@ -1,38 +1,19 @@
import six
from ..utils.is_base_type import is_base_type
from ..utils.trim_docstring import trim_docstring
from .options import Options
from .base import BaseOptions, BaseType
from .unmountedtype import UnmountedType
class UnionMeta(type):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Union
if not is_base_type(bases, UnionMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=trim_docstring(attrs.get('__doc__')),
types=(),
)
assert (
isinstance(options.types, (list, tuple)) and
len(options.types) > 0
), 'Must provide types for Union {}.'.format(options.name)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls): # noqa: N805
return cls._meta.name
# For static type checking with Mypy
MYPY = False
if MYPY:
from .objecttype import ObjectType # NOQA
from typing import Iterable, Type # NOQA
class Union(six.with_metaclass(UnionMeta, UnmountedType)):
class UnionOptions(BaseOptions):
types = () # type: Iterable[Type[ObjectType]]
class Union(UnmountedType, BaseType):
'''
Union Type Definition
@ -40,6 +21,16 @@ class Union(six.with_metaclass(UnionMeta, UnmountedType)):
is used to describe what types are possible as well as providing a function
to determine which type is actually used when the field is resolved.
'''
@classmethod
def __init_subclass_with_meta__(cls, types=None, **options):
assert (
isinstance(types, (list, tuple)) and
len(types) > 0
), 'Must provide types for Union {name}.'.format(name=cls.__name__)
_meta = UnionOptions(cls)
_meta.types = types
super(Union, cls).__init_subclass_with_meta__(_meta=_meta, **options)
@classmethod
def get_type(cls):
@ -50,7 +41,7 @@ class Union(six.with_metaclass(UnionMeta, UnmountedType)):
return cls
@classmethod
def resolve_type(cls, instance, context, info):
from .objecttype import ObjectType
def resolve_type(cls, instance, info):
from .objecttype import ObjectType # NOQA
if isinstance(instance, ObjectType):
return type(instance)

View File

@ -1,6 +1,7 @@
import inspect
from collections import OrderedDict
from functools import partial
from six import string_types
from ..utils.module_loading import import_string
@ -8,35 +9,6 @@ from .mountedtype import MountedType
from .unmountedtype import UnmountedType
def merge(*dicts):
'''
Merge the dicts into one
'''
merged = OrderedDict()
for _dict in dicts:
merged.update(_dict)
return merged
def get_base_fields(bases, _as=None):
'''
Get all the fields in the given bases
'''
fields = OrderedDict()
from ..types import AbstractType, Interface
# We allow inheritance in AbstractTypes and Interfaces but not ObjectTypes
inherited_bases = (AbstractType, Interface)
for base in bases:
if base in inherited_bases or not issubclass(base, inherited_bases):
continue
for name, field in base._meta.fields.items():
if name in fields:
continue
fields[name] = get_field_as(field, _as=_as)
return fields
def get_field_as(value, _as=None):
'''
Get type mounted
@ -49,7 +21,7 @@ def get_field_as(value, _as=None):
return _as.mounted(value)
def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True):
def yank_fields_from_attrs(attrs, _as=None, sort=True):
'''
Extract all the fields in given attributes (dict)
and return them ordered
@ -60,8 +32,6 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True):
if not field:
continue
fields_with_names.append((attname, field))
if delete:
del attrs[attname]
if sort:
fields_with_names = sorted(fields_with_names, key=lambda f: f[1])
@ -71,6 +41,6 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True):
def get_type(_type):
if isinstance(_type, string_types):
return import_string(_type)
if inspect.isfunction(_type) or type(_type) is partial:
if inspect.isfunction(_type) or isinstance(_type, partial):
return _type()
return _type

27
graphene/types/uuid.py Normal file
View File

@ -0,0 +1,27 @@
from __future__ import absolute_import
from uuid import UUID as _UUID
from graphql.language import ast
from .scalars import Scalar
class UUID(Scalar):
'''UUID'''
@staticmethod
def serialize(uuid):
if isinstance(uuid, str):
uuid = _UUID(uuid)
assert isinstance(uuid, _UUID), "Expected UUID instance, received {}".format(uuid)
return str(uuid)
@staticmethod
def parse_literal(node):
if isinstance(node, ast.StringValue):
return _UUID(node.value)
@staticmethod
def parse_value(value):
return _UUID(value)

View File

@ -0,0 +1,36 @@
import six
from ..pyutils.compat import signature, func_name
from .deprecated import warn_deprecation
def annotate(_func=None, _trigger_warning=True, **annotations):
if not six.PY2 and _trigger_warning:
warn_deprecation(
"annotate is intended for use in Python 2 only, as you can use type annotations Python 3.\n"
"Read more in https://docs.python.org/3/library/typing.html"
)
if not _func:
def _func(f):
return annotate(f, **annotations)
return _func
func_signature = signature(_func)
# We make sure the annotations are valid
for key, value in annotations.items():
assert key in func_signature.parameters, (
'The key {key} is not a function parameter in the function "{func_name}".'
).format(
key=key,
func_name=func_name(_func)
)
func_annotations = getattr(_func, '__annotations__', None)
if func_annotations is None:
_func.__annotations__ = annotations
else:
_func.__annotations__.update(annotations)
return _func

View File

@ -0,0 +1,80 @@
import functools
import inspect
import warnings
string_types = (type(b''), type(u''))
def warn_deprecation(text):
warnings.simplefilter('always', DeprecationWarning)
warnings.warn(
text,
category=DeprecationWarning,
stacklevel=2
)
warnings.simplefilter('default', DeprecationWarning)
def deprecated(reason):
"""
This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.
"""
if isinstance(reason, string_types):
# The @deprecated is used with a 'reason'.
#
# .. code-block:: python
#
# @deprecated("please, use another function")
# def old_function(x, y):
# pass
def decorator(func1):
if inspect.isclass(func1):
fmt1 = "Call to deprecated class {name} ({reason})."
else:
fmt1 = "Call to deprecated function {name} ({reason})."
@functools.wraps(func1)
def new_func1(*args, **kwargs):
warn_deprecation(
fmt1.format(name=func1.__name__, reason=reason),
)
return func1(*args, **kwargs)
return new_func1
return decorator
elif inspect.isclass(reason) or inspect.isfunction(reason):
# The @deprecated is used without any 'reason'.
#
# .. code-block:: python
#
# @deprecated
# def old_function(x, y):
# pass
func2 = reason
if inspect.isclass(func2):
fmt2 = "Call to deprecated class {name}."
else:
fmt2 = "Call to deprecated function {name}."
@functools.wraps(func2)
def new_func2(*args, **kwargs):
warn_deprecation(
fmt2.format(name=func2.__name__),
)
return func2(*args, **kwargs)
return new_func2
else:
raise TypeError(repr(type(reason)))

View File

@ -1,3 +0,0 @@
def is_base_type(bases, _type):
return any(b for b in bases if isinstance(b, _type))

View File

@ -1,8 +1,11 @@
from functools import wraps
from .deprecated import deprecated
@deprecated('This function is deprecated')
def resolve_only_args(func):
@wraps(func)
def inner(root, args, context, info):
def wrapped_func(root, info, **args):
return func(root, **args)
return inner
return wrapped_func

View File

@ -0,0 +1,44 @@
import six
from inspect import isclass
from ..pyutils.init_subclass import InitSubclassMeta
from .props import props
class SubclassWithMeta_Meta(InitSubclassMeta):
def __repr__(cls):
return cls._meta.name
class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_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):
"""This method just terminates the super() chain"""
_Meta = getattr(cls, "Meta", None)
_meta_props = {}
if _Meta:
if isinstance(_Meta, dict):
_meta_props = _Meta
elif isclass(_Meta):
_meta_props = props(_Meta)
else:
raise Exception("Meta have to be either a class or a dict. Received {}".format(_Meta))
delattr(cls, "Meta")
options = dict(meta_options, **_meta_props)
abstract = options.pop('abstract', False)
if abstract:
assert not options, (
"Abstract types can only contain the abstract attribute. "
"Received: abstract, {option_keys}"
).format(option_keys=', '.join(options.keys()))
else:
super_class = super(cls, cls)
if hasattr(super_class, '__init_subclass_with_meta__'):
super_class.__init_subclass_with_meta__(**options)
@classmethod
def __init_subclass_with_meta__(cls, **meta_options):
"""This method just terminates the super() chain"""

View File

@ -0,0 +1,33 @@
import pytest
from ..annotate import annotate
def func(a, b, *c, **d):
pass
annotations = {
'a': int,
'b': str,
'c': list,
'd': dict
}
def func_with_annotations(a, b, *c, **d):
pass
func_with_annotations.__annotations__ = annotations
def test_annotate_with_no_params():
annotated_func = annotate(func, _trigger_warning=False)
assert annotated_func.__annotations__ == {}
def test_annotate_with_params():
annotated_func = annotate(_trigger_warning=False, **annotations)(func)
assert annotated_func.__annotations__ == annotations
def test_annotate_with_wront_params():
with pytest.raises(Exception) as exc_info:
annotated_func = annotate(p=int, _trigger_warning=False)(func)
assert str(exc_info.value) == 'The key p is not a function parameter in the function "func".'

View File

@ -0,0 +1,65 @@
import pytest
from .. import deprecated
from ..deprecated import deprecated as deprecated_decorator, warn_deprecation
def test_warn_deprecation(mocker):
mocker.patch.object(deprecated.warnings, 'warn')
warn_deprecation("OH!")
deprecated.warnings.warn.assert_called_with('OH!', stacklevel=2, category=DeprecationWarning)
def test_deprecated_decorator(mocker):
mocker.patch.object(deprecated, 'warn_deprecation')
@deprecated_decorator
def my_func():
return True
result = my_func()
assert result
deprecated.warn_deprecation.assert_called_with("Call to deprecated function my_func.")
def test_deprecated_class(mocker):
mocker.patch.object(deprecated, 'warn_deprecation')
@deprecated_decorator
class X:
pass
result = X()
assert result
deprecated.warn_deprecation.assert_called_with("Call to deprecated class X.")
def test_deprecated_decorator_text(mocker):
mocker.patch.object(deprecated, 'warn_deprecation')
@deprecated_decorator("Deprecation text")
def my_func():
return True
result = my_func()
assert result
deprecated.warn_deprecation.assert_called_with("Call to deprecated function my_func (Deprecation text).")
def test_deprecated_class_text(mocker):
mocker.patch.object(deprecated, 'warn_deprecation')
@deprecated_decorator("Deprecation text")
class X:
pass
result = X()
assert result
deprecated.warn_deprecation.assert_called_with("Call to deprecated class X (Deprecation text).")
def test_deprecated_other_object(mocker):
mocker.patch.object(deprecated, 'warn_deprecation')
with pytest.raises(TypeError) as exc_info:
deprecated_decorator({})

View File

@ -1,16 +1,16 @@
from pytest import raises
from graphene import String
from graphene.types.objecttype import ObjectTypeMeta
from ..module_loading import lazy_import, import_string
from graphene import ObjectType, String
from ..module_loading import import_string, lazy_import
def test_import_string():
MyString = import_string('graphene.String')
assert MyString == String
MyObjectTypeMeta = import_string('graphene.ObjectType', '__class__')
assert MyObjectTypeMeta == ObjectTypeMeta
MyObjectTypeMeta = import_string('graphene.ObjectType', '__doc__')
assert MyObjectTypeMeta == ObjectType.__doc__
def test_import_string_module():
@ -52,6 +52,6 @@ def test_lazy_import():
MyString = f()
assert MyString == String
f = lazy_import('graphene.ObjectType', '__class__')
f = lazy_import('graphene.ObjectType', '__doc__')
MyObjectTypeMeta = f()
assert MyObjectTypeMeta == ObjectTypeMeta
assert MyObjectTypeMeta == ObjectType.__doc__

View File

@ -1,12 +1,15 @@
from ..resolve_only_args import resolve_only_args
from .. import deprecated
def test_resolve_only_args():
def resolver(*args, **kwargs):
return kwargs
def test_resolve_only_args(mocker):
mocker.patch.object(deprecated, 'warn_deprecation')
def resolver(root, **args):
return root, args
my_data = {'one': 1, 'two': 2}
wrapped = resolve_only_args(resolver)
assert wrapped(None, my_data, None, None) == my_data
wrapped_resolver = resolve_only_args(resolver)
assert deprecated.warn_deprecation.called
result = wrapped_resolver(1, 2, a=3)
assert result == (1, {'a': 3})

View File

@ -9,11 +9,10 @@ def test_trim_docstring():
Multiple paragraphs too
"""
pass
assert (trim_docstring(WellDocumentedObject.__doc__) ==
"This object is very well-documented. It has multiple lines in its\n"
"description.\n\nMultiple paragraphs too")
"This object is very well-documented. It has multiple lines in its\n"
"description.\n\nMultiple paragraphs too")
class UndocumentedObject(object):
pass

Some files were not shown because too many files have changed in this diff Show More