mirror of
https://github.com/graphql-python/graphene.git
synced 2024-12-01 22:14:02 +03:00
commit
0b92d3dba6
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -80,3 +80,4 @@ target/
|
||||||
# Databases
|
# Databases
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
.vscode
|
.vscode
|
||||||
|
.mypy_cache
|
||||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -2,8 +2,8 @@ language: python
|
||||||
sudo: false
|
sudo: false
|
||||||
python:
|
python:
|
||||||
- 2.7
|
- 2.7
|
||||||
- 3.4
|
|
||||||
- 3.5
|
- 3.5
|
||||||
|
- 3.6
|
||||||
- pypy
|
- pypy
|
||||||
before_install:
|
before_install:
|
||||||
- |
|
- |
|
||||||
|
@ -26,6 +26,8 @@ install:
|
||||||
python setup.py develop
|
python setup.py develop
|
||||||
elif [ "$TEST_TYPE" = lint ]; then
|
elif [ "$TEST_TYPE" = lint ]; then
|
||||||
pip install flake8
|
pip install flake8
|
||||||
|
elif [ "$TEST_TYPE" = mypy ]; then
|
||||||
|
pip install mypy
|
||||||
fi
|
fi
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
|
@ -33,6 +35,10 @@ script:
|
||||||
echo "Checking Python code lint."
|
echo "Checking Python code lint."
|
||||||
flake8 graphene
|
flake8 graphene
|
||||||
exit
|
exit
|
||||||
|
elif [ "$TEST_TYPE" = mypy ]; then
|
||||||
|
echo "Checking Python types."
|
||||||
|
mypy graphene
|
||||||
|
exit
|
||||||
elif [ "$TEST_TYPE" = build ]; then
|
elif [ "$TEST_TYPE" = build ]; then
|
||||||
py.test --cov=graphene graphene examples
|
py.test --cov=graphene graphene examples
|
||||||
fi
|
fi
|
||||||
|
@ -51,6 +57,8 @@ matrix:
|
||||||
include:
|
include:
|
||||||
- python: '2.7'
|
- python: '2.7'
|
||||||
env: TEST_TYPE=lint
|
env: TEST_TYPE=lint
|
||||||
|
- python: '3.6'
|
||||||
|
env: TEST_TYPE=mypy
|
||||||
deploy:
|
deploy:
|
||||||
provider: pypi
|
provider: pypi
|
||||||
user: syrusakbary
|
user: syrusakbary
|
||||||
|
@ -58,3 +66,4 @@ deploy:
|
||||||
tags: true
|
tags: true
|
||||||
password:
|
password:
|
||||||
secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ=
|
secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ=
|
||||||
|
distributions: "sdist bdist_wheel"
|
||||||
|
|
10
README.md
10
README.md
|
@ -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
|
For instaling graphene, just run this command in your shell
|
||||||
|
|
||||||
```bash
|
```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
|
## Examples
|
||||||
|
@ -48,7 +48,7 @@ Here is one example for you to get started:
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
hello = graphene.String(description='A typical hello world')
|
hello = graphene.String(description='A typical hello world')
|
||||||
|
|
||||||
def resolve_hello(self, args, context, info):
|
def resolve_hello(self, info):
|
||||||
return 'World'
|
return 'World'
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
|
|
14
README.rst
14
README.rst
|
@ -1,5 +1,5 @@
|
||||||
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 to Graphene ``1.0``.
|
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
|
- **Easy to use:** Graphene helps you use GraphQL in Python without
|
||||||
effort.
|
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
|
- **Data agnostic:** Graphene supports any kind of data source: SQL
|
||||||
(Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe
|
(Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe
|
||||||
that by providing a complete API you could plug Graphene anywhere
|
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
|
.. 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.
|
upgrade.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
|
@ -65,7 +65,7 @@ Here is one example for you to get started:
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
hello = graphene.String(description='A typical hello world')
|
hello = graphene.String(description='A typical hello world')
|
||||||
|
|
||||||
def resolve_hello(self, args, context, info):
|
def resolve_hello(self, info):
|
||||||
return 'World'
|
return 'World'
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
|
|
293
UPGRADE-v2.0.md
Normal file
293
UPGRADE-v2.0.md
Normal 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`.
|
|
@ -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)
|
best_friend = graphene.Field(lambda: User)
|
||||||
friends = graphene.List(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)
|
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)
|
return user_loader.load_many(self.friend_ids)
|
||||||
|
|
|
@ -24,8 +24,8 @@ You can pass context to a query via ``context_value``.
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
|
|
||||||
def resolve_name(self, args, context, info):
|
def resolve_name(self, info):
|
||||||
return context.get('name')
|
return info.context.get('name')
|
||||||
|
|
||||||
schema = graphene.Schema(Query)
|
schema = graphene.Schema(Query)
|
||||||
result = schema.execute('{ name }', context_value={'name': 'Syrus'})
|
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):
|
class Query(graphene.ObjectType):
|
||||||
user = graphene.Field(User)
|
user = graphene.Field(User)
|
||||||
|
|
||||||
def resolve_user(self, args, context, info):
|
def resolve_user(self, info):
|
||||||
return context.get('user')
|
return info.context.get('user')
|
||||||
|
|
||||||
schema = graphene.Schema(Query)
|
schema = graphene.Schema(Query)
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
|
|
|
@ -31,10 +31,10 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'`
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class AuthorizationMiddleware(object):
|
class AuthorizationMiddleware(object):
|
||||||
def resolve(self, next, root, args, context, info):
|
def resolve(self, next, root, info, **args):
|
||||||
if info.field_name == 'user':
|
if info.field_name == 'user':
|
||||||
return None
|
return None
|
||||||
return next(root, args, context, info)
|
return next(root, info, **args)
|
||||||
|
|
||||||
|
|
||||||
And then execute it with:
|
And then execute it with:
|
||||||
|
|
|
@ -12,15 +12,15 @@ Let’s build a basic GraphQL schema from scratch.
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- Python (2.7, 3.2, 3.3, 3.4, 3.5, pypy)
|
- Python (2.7, 3.4, 3.5, 3.6, pypy)
|
||||||
- Graphene (1.0)
|
- Graphene (2.0)
|
||||||
|
|
||||||
Project setup
|
Project setup
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
pip install "graphene>=1.0"
|
pip install "graphene>=2.0"
|
||||||
|
|
||||||
Creating a basic Schema
|
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
|
import graphene
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
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):
|
def resolve_hello(self, info, name):
|
||||||
return 'Hello ' + args['name']
|
return 'Hello ' + name
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
|
|
||||||
|
|
|
@ -41,5 +41,5 @@ that implements ``Node`` will have a default Connection.
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
ships = relay.ConnectionField(ShipConnection)
|
ships = relay.ConnectionField(ShipConnection)
|
||||||
|
|
||||||
def resolve_ships(self, args, context, info):
|
def resolve_ships(self, info):
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -21,9 +21,9 @@ subclass of ``relay.ClientIDMutation``.
|
||||||
faction = graphene.Field(Faction)
|
faction = graphene.Field(Faction)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, input, context, info):
|
def mutate_and_get_payload(cls, root, info, **input):
|
||||||
ship_name = input.get('ship_name')
|
ship_name = input.ship_name
|
||||||
faction_id = input.get('faction_id')
|
faction_id = input.faction_id
|
||||||
ship = create_ship(ship_name, faction_id)
|
ship = create_ship(ship_name, faction_id)
|
||||||
faction = get_faction(faction_id)
|
faction = get_faction(faction_id)
|
||||||
return IntroduceShip(ship=ship, faction=faction)
|
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()
|
success = graphene.String()
|
||||||
|
|
||||||
@classmethod
|
@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
|
# When using it in Django, context will be the request
|
||||||
files = context.FILES
|
files = context.FILES
|
||||||
# Or, if used in Flask, context will be the flask global request
|
# Or, if used in Flask, context will be the flask global request
|
||||||
|
|
|
@ -22,7 +22,7 @@ Example usage (taken from the `Starwars Relay example`_):
|
||||||
name = graphene.String(description='The name of the ship.')
|
name = graphene.String(description='The name of the ship.')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id, context, info):
|
def get_node(cls, info, id):
|
||||||
return get_ship(id)
|
return get_ship(id)
|
||||||
|
|
||||||
The ``id`` returned by the ``Ship`` type when you query it will be a
|
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)
|
return '{}:{}'.format(type, id)
|
||||||
|
|
||||||
@staticmethod
|
@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(':')
|
type, id = global_id.split(':')
|
||||||
if only_node:
|
if only_node:
|
||||||
# We assure that the node type that we want to retrieve
|
# We assure that the node type that we want to retrieve
|
||||||
|
|
|
@ -13,15 +13,14 @@ This example defines a Mutation:
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
class CreatePerson(graphene.Mutation):
|
class CreatePerson(graphene.Mutation):
|
||||||
class Input:
|
class Arguments:
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
|
|
||||||
ok = graphene.Boolean()
|
ok = graphene.Boolean()
|
||||||
person = graphene.Field(lambda: Person)
|
person = graphene.Field(lambda: Person)
|
||||||
|
|
||||||
@staticmethod
|
def mutate(self, name):
|
||||||
def mutate(root, args, context, info):
|
person = Person(name=name)
|
||||||
person = Person(name=args.get('name'))
|
|
||||||
ok = True
|
ok = True
|
||||||
return CreatePerson(person=person, ok=ok)
|
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
|
To use an InputField you define an InputObjectType that specifies the structure of your input data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
class PersonInput(graphene.InputObjectType):
|
class PersonInput(graphene.InputObjectType):
|
||||||
name = graphene.String()
|
name = graphene.String(required=True)
|
||||||
age = graphene.Int()
|
age = graphene.Int(required=True)
|
||||||
|
|
||||||
class CreatePerson(graphene.Mutation):
|
class CreatePerson(graphene.Mutation):
|
||||||
class Input:
|
class Arguments:
|
||||||
person_data = graphene.Argument(PersonInput)
|
person_data = PersonInput(required=True)
|
||||||
|
|
||||||
person = graphene.Field(lambda: Person)
|
person = graphene.Field(Person)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate(root, args, context, info):
|
def mutate(root, person_data=None):
|
||||||
p_data = args.get('person_data')
|
|
||||||
|
|
||||||
name = p_data.get('name')
|
name = p_data.get('name')
|
||||||
age = p_data.get('age')
|
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)
|
return CreatePerson(person=person)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ This example model defines a Person, with a first and a last name:
|
||||||
last_name = graphene.String()
|
last_name = graphene.String()
|
||||||
full_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)
|
return '{} {}'.format(self.first_name, self.last_name)
|
||||||
|
|
||||||
**first\_name** and **last\_name** are fields of the ObjectType. Each
|
**first\_name** and **last\_name** are fields of the ObjectType. Each
|
||||||
|
@ -71,8 +71,7 @@ method in the class.
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
reverse = graphene.String(word=graphene.String())
|
reverse = graphene.String(word=graphene.String())
|
||||||
|
|
||||||
def resolve_reverse(self, args, context, info):
|
def resolve_reverse(self, info, word):
|
||||||
word = args.get('word')
|
|
||||||
return word[::-1]
|
return word[::-1]
|
||||||
|
|
||||||
Resolvers outside the class
|
Resolvers outside the class
|
||||||
|
@ -84,8 +83,7 @@ A field can use a custom resolver from outside the class:
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
def reverse(root, args, context, info):
|
def reverse(root, info, word):
|
||||||
word = args.get('word')
|
|
||||||
return word[::-1]
|
return word[::-1]
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
|
63
docs/types/unions.rst
Normal file
63
docs/types/unions.rst
Normal 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
|
||||||
|
|
|
@ -11,10 +11,9 @@ class Address(graphene.ObjectType):
|
||||||
|
|
||||||
|
|
||||||
class Query(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):
|
def resolve_address(self, info, geo):
|
||||||
geo = args.get('geo')
|
|
||||||
return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng')))
|
return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng')))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,9 @@ class User(graphene.ObjectType):
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
me = graphene.Field(User)
|
me = graphene.Field(User)
|
||||||
|
|
||||||
def resolve_me(self, args, context, info):
|
def resolve_me(self, info):
|
||||||
return context['user']
|
return info.context['user']
|
||||||
|
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
query = '''
|
query = '''
|
||||||
|
|
|
@ -11,9 +11,10 @@ class Query(graphene.ObjectType):
|
||||||
|
|
||||||
patron = graphene.Field(Patron)
|
patron = graphene.Field(Patron)
|
||||||
|
|
||||||
def resolve_patron(self, args, context, info):
|
def resolve_patron(self, info):
|
||||||
return Patron(id=1, name='Syrus', age=27)
|
return Patron(id=1, name='Syrus', age=27)
|
||||||
|
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
query = '''
|
query = '''
|
||||||
query something{
|
query something{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import resolve_only_args
|
|
||||||
|
|
||||||
from .data import get_character, get_droid, get_hero, get_human
|
from .data import get_character, get_droid, get_hero, get_human
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ class Character(graphene.Interface):
|
||||||
friends = graphene.List(lambda: Character)
|
friends = graphene.List(lambda: Character)
|
||||||
appears_in = graphene.List(Episode)
|
appears_in = graphene.List(Episode)
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
def resolve_friends(self, info):
|
||||||
# The character friends is a list of strings
|
# The character friends is a list of strings
|
||||||
return [get_character(f) for f in self.friends]
|
return [get_character(f) for f in self.friends]
|
||||||
|
|
||||||
|
@ -46,16 +45,13 @@ class Query(graphene.ObjectType):
|
||||||
id=graphene.String()
|
id=graphene.String()
|
||||||
)
|
)
|
||||||
|
|
||||||
@resolve_only_args
|
def resolve_hero(self, info, episode=None):
|
||||||
def resolve_hero(self, episode=None):
|
|
||||||
return get_hero(episode)
|
return get_hero(episode)
|
||||||
|
|
||||||
@resolve_only_args
|
def resolve_human(self, info, id):
|
||||||
def resolve_human(self, id):
|
|
||||||
return get_human(id)
|
return get_human(id)
|
||||||
|
|
||||||
@resolve_only_args
|
def resolve_droid(self, info, id):
|
||||||
def resolve_droid(self, id):
|
|
||||||
return get_droid(id)
|
return get_droid(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from graphene.test import Client
|
from graphene.test import Client
|
||||||
|
|
||||||
from ..data import setup
|
from ..data import setup
|
||||||
from ..schema import schema
|
from ..schema import schema
|
||||||
|
|
||||||
|
@ -6,6 +7,7 @@ setup()
|
||||||
|
|
||||||
client = Client(schema)
|
client = Client(schema)
|
||||||
|
|
||||||
|
|
||||||
def test_hero_name_query(snapshot):
|
def test_hero_name_query(snapshot):
|
||||||
query = '''
|
query = '''
|
||||||
query HeroNameQuery {
|
query HeroNameQuery {
|
||||||
|
@ -17,7 +19,6 @@ def test_hero_name_query(snapshot):
|
||||||
snapshot.assert_match(client.execute(query))
|
snapshot.assert_match(client.execute(query))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_hero_name_and_friends_query(snapshot):
|
def test_hero_name_and_friends_query(snapshot):
|
||||||
query = '''
|
query = '''
|
||||||
query HeroNameAndFriendsQuery {
|
query HeroNameAndFriendsQuery {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import graphene
|
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
|
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.')
|
name = graphene.String(description='The name of the ship.')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id, context, info):
|
def get_node(cls, info, id):
|
||||||
return get_ship(id)
|
return get_ship(id)
|
||||||
|
|
||||||
|
|
||||||
|
class ShipConnection(relay.Connection):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
node = Ship
|
||||||
|
|
||||||
|
|
||||||
class Faction(graphene.ObjectType):
|
class Faction(graphene.ObjectType):
|
||||||
'''A faction in the Star Wars saga'''
|
'''A faction in the Star Wars saga'''
|
||||||
|
|
||||||
|
@ -24,15 +30,14 @@ class Faction(graphene.ObjectType):
|
||||||
interfaces = (relay.Node, )
|
interfaces = (relay.Node, )
|
||||||
|
|
||||||
name = graphene.String(description='The name of the faction.')
|
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, info, **args):
|
||||||
def resolve_ships(self, **args):
|
|
||||||
# Transform the instance ship_ids into real instances
|
# Transform the instance ship_ids into real instances
|
||||||
return [get_ship(ship_id) for ship_id in self.ships]
|
return [get_ship(ship_id) for ship_id in self.ships]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id, context, info):
|
def get_node(cls, info, id):
|
||||||
return get_faction(id)
|
return get_faction(id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,9 +51,7 @@ class IntroduceShip(relay.ClientIDMutation):
|
||||||
faction = graphene.Field(Faction)
|
faction = graphene.Field(Faction)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, input, context, info):
|
def mutate_and_get_payload(cls, root, info, ship_name, faction_id, client_mutation_id=None):
|
||||||
ship_name = input.get('ship_name')
|
|
||||||
faction_id = input.get('faction_id')
|
|
||||||
ship = create_ship(ship_name, faction_id)
|
ship = create_ship(ship_name, faction_id)
|
||||||
faction = get_faction(faction_id)
|
faction = get_faction(faction_id)
|
||||||
return IntroduceShip(ship=ship, faction=faction)
|
return IntroduceShip(ship=ship, faction=faction)
|
||||||
|
@ -59,12 +62,10 @@ class Query(graphene.ObjectType):
|
||||||
empire = graphene.Field(Faction)
|
empire = graphene.Field(Faction)
|
||||||
node = relay.Node.Field()
|
node = relay.Node.Field()
|
||||||
|
|
||||||
@resolve_only_args
|
def resolve_rebels(self, info):
|
||||||
def resolve_rebels(self):
|
|
||||||
return get_rebels()
|
return get_rebels()
|
||||||
|
|
||||||
@resolve_only_args
|
def resolve_empire(self, info):
|
||||||
def resolve_empire(self):
|
|
||||||
return get_empire()
|
return get_empire()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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!
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from graphene.test import Client
|
from graphene.test import Client
|
||||||
|
|
||||||
from ..data import setup
|
from ..data import setup
|
||||||
from ..schema import schema
|
from ..schema import schema
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from graphene.test import Client
|
from graphene.test import Client
|
||||||
|
|
||||||
from ..data import setup
|
from ..data import setup
|
||||||
from ..schema import schema
|
from ..schema import schema
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from graphene.test import Client
|
from graphene.test import Client
|
||||||
|
|
||||||
from ..data import setup
|
from ..data import setup
|
||||||
from ..schema import schema
|
from ..schema import schema
|
||||||
|
|
||||||
|
@ -7,66 +8,8 @@ setup()
|
||||||
client = Client(schema)
|
client = Client(schema)
|
||||||
|
|
||||||
|
|
||||||
def test_str_schema():
|
def test_str_schema(snapshot):
|
||||||
assert str(schema) == '''schema {
|
snapshot.assert_match(str(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_correctly_fetches_id_name_rebels(snapshot):
|
def test_correctly_fetches_id_name_rebels(snapshot):
|
||||||
|
|
|
@ -1,22 +1,6 @@
|
||||||
from .pyutils.version import get_version
|
from .pyutils.version import get_version
|
||||||
|
|
||||||
|
from .types import (
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
VERSION = (1, 4, 1, 'final', 0)
|
|
||||||
|
|
||||||
__version__ = get_version(VERSION)
|
|
||||||
|
|
||||||
if not __SETUP__:
|
|
||||||
|
|
||||||
from .types import (
|
|
||||||
AbstractType,
|
AbstractType,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
InputObjectType,
|
InputObjectType,
|
||||||
|
@ -27,13 +11,17 @@ if not __SETUP__:
|
||||||
Schema,
|
Schema,
|
||||||
Scalar,
|
Scalar,
|
||||||
String, ID, Int, Float, Boolean,
|
String, ID, Int, Float, Boolean,
|
||||||
|
JSONString,
|
||||||
|
UUID,
|
||||||
List, NonNull,
|
List, NonNull,
|
||||||
Enum,
|
Enum,
|
||||||
Argument,
|
Argument,
|
||||||
Dynamic,
|
Dynamic,
|
||||||
Union,
|
Union,
|
||||||
)
|
Context,
|
||||||
from .relay import (
|
ResolveInfo
|
||||||
|
)
|
||||||
|
from .relay import (
|
||||||
Node,
|
Node,
|
||||||
is_node,
|
is_node,
|
||||||
GlobalID,
|
GlobalID,
|
||||||
|
@ -41,12 +29,17 @@ if not __SETUP__:
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionField,
|
ConnectionField,
|
||||||
PageInfo
|
PageInfo
|
||||||
)
|
)
|
||||||
from .utils.resolve_only_args import resolve_only_args
|
from .utils.resolve_only_args import resolve_only_args
|
||||||
from .utils.module_loading import lazy_import
|
from .utils.module_loading import lazy_import
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'AbstractType',
|
VERSION = (2, 0, 0, 'alpha', 0)
|
||||||
|
|
||||||
|
__version__ = get_version(VERSION)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'__version__',
|
||||||
'ObjectType',
|
'ObjectType',
|
||||||
'InputObjectType',
|
'InputObjectType',
|
||||||
'Interface',
|
'Interface',
|
||||||
|
@ -61,6 +54,8 @@ if not __SETUP__:
|
||||||
'Float',
|
'Float',
|
||||||
'Enum',
|
'Enum',
|
||||||
'Boolean',
|
'Boolean',
|
||||||
|
'JSONString',
|
||||||
|
'UUID',
|
||||||
'List',
|
'List',
|
||||||
'NonNull',
|
'NonNull',
|
||||||
'Argument',
|
'Argument',
|
||||||
|
@ -75,4 +70,9 @@ if not __SETUP__:
|
||||||
'ConnectionField',
|
'ConnectionField',
|
||||||
'PageInfo',
|
'PageInfo',
|
||||||
'lazy_import',
|
'lazy_import',
|
||||||
]
|
'Context',
|
||||||
|
'ResolveInfo',
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
'AbstractType',
|
||||||
|
]
|
||||||
|
|
19
graphene/pyutils/compat.py
Normal file
19
graphene/pyutils/compat.py
Normal 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__
|
19
graphene/pyutils/init_subclass.py
Normal file
19
graphene/pyutils/init_subclass.py
Normal 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
|
808
graphene/pyutils/signature.py
Normal file
808
graphene/pyutils/signature.py
Normal 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
|
|
@ -21,6 +21,7 @@ def test__is_dunder():
|
||||||
for name in non_dunder_names:
|
for name in non_dunder_names:
|
||||||
assert _is_dunder(name) is False
|
assert _is_dunder(name) is False
|
||||||
|
|
||||||
|
|
||||||
def test__is_sunder():
|
def test__is_sunder():
|
||||||
sunder_names = [
|
sunder_names = [
|
||||||
'_i_',
|
'_i_',
|
||||||
|
|
|
@ -2,18 +2,13 @@ import re
|
||||||
from collections import Iterable, OrderedDict
|
from collections import Iterable, OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from graphql_relay import connection_from_list
|
from graphql_relay import connection_from_list
|
||||||
from promise import Promise, is_thenable
|
from promise import Promise, is_thenable
|
||||||
|
|
||||||
from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String,
|
from ..types import (Boolean, Enum, Int, Interface, List, NonNull, Scalar,
|
||||||
Union)
|
String, Union)
|
||||||
from ..types.field import Field
|
from ..types.field import Field
|
||||||
from ..types.objecttype import ObjectType, ObjectTypeMeta
|
from ..types.objecttype import ObjectType, ObjectTypeOptions
|
||||||
from ..types.options import Options
|
|
||||||
from ..utils.is_base_type import is_base_type
|
|
||||||
from ..utils.props import props
|
|
||||||
from .node import is_node
|
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(
|
class Connection(ObjectType):
|
||||||
attrs.pop('Meta', None),
|
|
||||||
name=name,
|
|
||||||
description=None,
|
|
||||||
node=None,
|
|
||||||
)
|
|
||||||
options.interfaces = ()
|
|
||||||
options.local_fields = OrderedDict()
|
|
||||||
|
|
||||||
assert options.node, 'You have to provide a node in {}.Meta'.format(cls.__name__)
|
class Meta:
|
||||||
assert issubclass(options.node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), (
|
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 {}.'
|
'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
|
base_name = re.sub('Connection$', '', name or cls.__name__) or node._meta.name
|
||||||
if not options.name:
|
if not name:
|
||||||
options.name = '{}Connection'.format(base_name)
|
name = '{}Connection'.format(base_name)
|
||||||
|
|
||||||
edge_class = attrs.pop('Edge', None)
|
edge_class = getattr(cls, 'Edge', None)
|
||||||
|
_node = node
|
||||||
|
|
||||||
class EdgeBase(AbstractType):
|
class EdgeBase(object):
|
||||||
node = Field(options.node, description='The item at the end of the edge')
|
node = Field(_node, description='The item at the end of the edge')
|
||||||
cursor = String(required=True, description='A cursor for use in pagination')
|
cursor = String(required=True, description='A cursor for use in pagination')
|
||||||
|
|
||||||
edge_name = '{}Edge'.format(base_name)
|
edge_name = '{}Edge'.format(base_name)
|
||||||
if edge_class and issubclass(edge_class, AbstractType):
|
if edge_class:
|
||||||
edge = type(edge_name, (EdgeBase, edge_class, ObjectType, ), {})
|
edge_bases = (edge_class, EdgeBase, ObjectType,)
|
||||||
else:
|
else:
|
||||||
edge_attrs = props(edge_class) if edge_class else {}
|
edge_bases = (EdgeBase, ObjectType,)
|
||||||
edge = type(edge_name, (EdgeBase, ObjectType, ), edge_attrs)
|
|
||||||
|
|
||||||
class ConnectionBase(AbstractType):
|
edge = type(edge_name, edge_bases, {})
|
||||||
page_info = Field(PageInfo, name='pageInfo', required=True)
|
cls.Edge = edge
|
||||||
edges = NonNull(List(edge))
|
|
||||||
|
|
||||||
bases = (ConnectionBase, ) + bases
|
_meta.name = name
|
||||||
attrs = dict(attrs, _meta=options, Edge=edge)
|
_meta.node = node
|
||||||
return ObjectTypeMeta.__new__(cls, name, bases, attrs)
|
_meta.fields = OrderedDict([
|
||||||
|
('page_info', Field(PageInfo, name='pageInfo', required=True)),
|
||||||
|
('edges', Field(NonNull(List(edge)))),
|
||||||
class Connection(six.with_metaclass(ConnectionMeta, ObjectType)):
|
])
|
||||||
pass
|
return super(Connection, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||||
|
|
||||||
|
|
||||||
class IterableConnectionField(Field):
|
class IterableConnectionField(Field):
|
||||||
|
@ -109,10 +98,13 @@ class IterableConnectionField(Field):
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
type = super(IterableConnectionField, self).type
|
type = super(IterableConnectionField, self).type
|
||||||
if is_node(type):
|
|
||||||
connection_type = type.Connection
|
|
||||||
else:
|
|
||||||
connection_type = type
|
connection_type = type
|
||||||
|
if is_node(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), (
|
assert issubclass(connection_type, Connection), (
|
||||||
'{} type have to be a subclass of Connection. Received "{}".'
|
'{} type have to be a subclass of Connection. Received "{}".'
|
||||||
).format(self.__class__.__name__, connection_type)
|
).format(self.__class__.__name__, connection_type)
|
||||||
|
@ -138,8 +130,8 @@ class IterableConnectionField(Field):
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def connection_resolver(cls, resolver, connection_type, root, args, context, info):
|
def connection_resolver(cls, resolver, connection_type, root, info, **args):
|
||||||
resolved = resolver(root, args, context, info)
|
resolved = resolver(root, info, **args)
|
||||||
|
|
||||||
on_resolve = partial(cls.resolve_connection, connection_type, args)
|
on_resolve = partial(cls.resolve_connection, connection_type, args)
|
||||||
if is_thenable(resolved):
|
if is_thenable(resolved):
|
||||||
|
|
|
@ -1,68 +1,72 @@
|
||||||
import re
|
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, InputObjectType, String
|
||||||
|
from ..types.mutation import Mutation
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class ClientIDMutationMeta(MutationMeta):
|
class ClientIDMutation(Mutation):
|
||||||
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)
|
|
||||||
|
|
||||||
input_class = attrs.pop('Input', None)
|
class Meta:
|
||||||
base_name = re.sub('Payload$', '', name)
|
abstract = True
|
||||||
if 'client_mutation_id' not in attrs:
|
|
||||||
attrs['client_mutation_id'] = String(name='clientMutationId')
|
@classmethod
|
||||||
cls = ObjectTypeMeta.__new__(cls, '{}Payload'.format(base_name), bases,
|
def __init_subclass_with_meta__(cls, output=None, input_fields=None,
|
||||||
attrs)
|
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)
|
mutate_and_get_payload = getattr(cls, 'mutate_and_get_payload', None)
|
||||||
if cls.mutate and cls.mutate.__func__ == ClientIDMutation.mutate.__func__:
|
if cls.mutate and cls.mutate.__func__ == ClientIDMutation.mutate.__func__:
|
||||||
assert mutate_and_get_payload, (
|
assert mutate_and_get_payload, (
|
||||||
"{}.mutate_and_get_payload method is required"
|
"{name}.mutate_and_get_payload method is required"
|
||||||
" in a ClientIDMutation.").format(name)
|
" in a ClientIDMutation.").format(name=name or cls.__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
|
|
||||||
|
|
||||||
|
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
|
@classmethod
|
||||||
def mutate(cls, root, args, context, info):
|
def mutate(cls, root, info, input):
|
||||||
input = args.get('input')
|
|
||||||
|
|
||||||
def on_resolve(payload):
|
def on_resolve(payload):
|
||||||
try:
|
try:
|
||||||
payload.client_mutation_id = input.get('clientMutationId')
|
payload.client_mutation_id = input.get('client_mutation_id')
|
||||||
except:
|
except:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
('Cannot set client_mutation_id in the payload object {}'
|
('Cannot set client_mutation_id in the payload object {}'
|
||||||
).format(repr(payload)))
|
).format(repr(payload)))
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
return Promise.resolve(
|
result = cls.mutate_and_get_payload(root, info, **input)
|
||||||
cls.mutate_and_get_payload(input, context, info)).then(on_resolve)
|
if is_thenable(result):
|
||||||
|
return Promise.resolve(result).then(on_resolve)
|
||||||
|
|
||||||
|
return on_resolve(result)
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
from collections import OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from graphql_relay import from_global_id, to_global_id
|
from graphql_relay import from_global_id, to_global_id
|
||||||
|
|
||||||
from ..types import ID, Field, Interface, ObjectType
|
from ..types import ID, Field, Interface, ObjectType
|
||||||
|
from ..types.interface import InterfaceOptions
|
||||||
from ..types.utils import get_type
|
from ..types.utils import get_type
|
||||||
from ..types.interface import InterfaceMeta
|
|
||||||
|
|
||||||
|
|
||||||
def is_node(objecttype):
|
def is_node(objecttype):
|
||||||
|
@ -22,18 +21,6 @@ def is_node(objecttype):
|
||||||
return False
|
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):
|
class GlobalID(Field):
|
||||||
|
|
||||||
def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs):
|
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
|
self.parent_type_name = parent_type._meta.name if parent_type else None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def id_resolver(parent_resolver, node, root, args, context, info, parent_type_name=None):
|
def id_resolver(parent_resolver, node, root, info, parent_type_name=None, **args):
|
||||||
type_id = parent_resolver(root, args, context, info)
|
type_id = parent_resolver(root, info, **args)
|
||||||
parent_type_name = parent_type_name or info.parent_type.name
|
parent_type_name = parent_type_name or info.parent_type.name
|
||||||
return node.to_global_id(parent_type_name, type_id) # root._meta.name
|
return node.to_global_id(parent_type_name, type_id) # root._meta.name
|
||||||
|
|
||||||
def get_resolver(self, parent_resolver):
|
def get_resolver(self, parent_resolver):
|
||||||
return partial(self.id_resolver, parent_resolver, self.node, parent_type_name=self.parent_type_name)
|
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
|
|
||||||
|
|
||||||
|
|
||||||
class NodeField(Field):
|
class NodeField(Field):
|
||||||
|
@ -75,10 +56,24 @@ class NodeField(Field):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_resolver(self, parent_resolver):
|
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'''
|
'''An object with an ID'''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -86,11 +81,11 @@ class Node(six.with_metaclass(NodeMeta, Interface)):
|
||||||
return NodeField(cls, *args, **kwargs)
|
return NodeField(cls, *args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def node_resolver(cls, root, args, context, info, only_type=None):
|
def node_resolver(cls, only_type, root, info, id):
|
||||||
return cls.get_node_from_global_id(args.get('id'), context, info, only_type)
|
return cls.get_node_from_global_id(info, id, only_type=only_type)
|
||||||
|
|
||||||
@classmethod
|
@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:
|
try:
|
||||||
_type, _id = cls.from_global_id(global_id)
|
_type, _id = cls.from_global_id(global_id)
|
||||||
graphene_type = info.schema.get_type(_type).graphene_type
|
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)
|
get_node = getattr(graphene_type, 'get_node', None)
|
||||||
if get_node:
|
if get_node:
|
||||||
return get_node(_id, context, info)
|
return get_node(info, _id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_global_id(cls, global_id):
|
def from_global_id(cls, global_id):
|
||||||
|
@ -117,11 +112,3 @@ class Node(six.with_metaclass(NodeMeta, Interface)):
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_global_id(cls, type, id):
|
def to_global_id(cls, type, id):
|
||||||
return to_global_id(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()
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from ...types import AbstractType, Field, List, NonNull, ObjectType, String, Argument, Int
|
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String
|
||||||
from ..connection import Connection, PageInfo, ConnectionField
|
from ..connection import Connection, ConnectionField, PageInfo
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ def test_connection():
|
||||||
|
|
||||||
|
|
||||||
def test_connection_inherit_abstracttype():
|
def test_connection_inherit_abstracttype():
|
||||||
class BaseConnection(AbstractType):
|
class BaseConnection(object):
|
||||||
extra = String()
|
extra = String()
|
||||||
|
|
||||||
class MyObjectConnection(BaseConnection, Connection):
|
class MyObjectConnection(BaseConnection, Connection):
|
||||||
|
@ -73,7 +74,7 @@ def test_edge():
|
||||||
|
|
||||||
|
|
||||||
def test_edge_with_bases():
|
def test_edge_with_bases():
|
||||||
class BaseEdge(AbstractType):
|
class BaseEdge(object):
|
||||||
extra = String()
|
extra = String()
|
||||||
|
|
||||||
class MyObjectConnection(Connection):
|
class MyObjectConnection(Connection):
|
||||||
|
@ -96,16 +97,6 @@ def test_edge_with_bases():
|
||||||
assert edge_fields['other'].type == String
|
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():
|
def test_pageinfo():
|
||||||
assert PageInfo._meta.name == 'PageInfo'
|
assert PageInfo._meta.name == 'PageInfo'
|
||||||
fields = PageInfo._meta.fields
|
fields = PageInfo._meta.fields
|
||||||
|
@ -114,6 +105,7 @@ def test_pageinfo():
|
||||||
|
|
||||||
def test_connectionfield():
|
def test_connectionfield():
|
||||||
class MyObjectConnection(Connection):
|
class MyObjectConnection(Connection):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
node = MyObject
|
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():
|
def test_connectionfield_custom_args():
|
||||||
class MyObjectConnection(Connection):
|
class MyObjectConnection(Connection):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
node = MyObject
|
node = MyObject
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from graphql_relay.utils import base64
|
||||||
from promise import Promise
|
from promise import Promise
|
||||||
|
|
||||||
from ...types import ObjectType, Schema, String
|
from ...types import ObjectType, Schema, String
|
||||||
from ..connection import ConnectionField, PageInfo
|
from ..connection import Connection, ConnectionField, PageInfo
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
|
|
||||||
letter_chars = ['A', 'B', 'C', 'D', 'E']
|
letter_chars = ['A', 'B', 'C', 'D', 'E']
|
||||||
|
@ -18,27 +18,33 @@ class Letter(ObjectType):
|
||||||
letter = String()
|
letter = String()
|
||||||
|
|
||||||
|
|
||||||
|
class LetterConnection(Connection):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
node = Letter
|
||||||
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
letters = ConnectionField(Letter)
|
letters = ConnectionField(LetterConnection)
|
||||||
connection_letters = ConnectionField(Letter)
|
connection_letters = ConnectionField(LetterConnection)
|
||||||
promise_letters = ConnectionField(Letter)
|
promise_letters = ConnectionField(LetterConnection)
|
||||||
|
|
||||||
node = Node.Field()
|
node = Node.Field()
|
||||||
|
|
||||||
def resolve_letters(self, args, context, info):
|
def resolve_letters(self, info, **args):
|
||||||
return list(letters.values())
|
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()))
|
return Promise.resolve(list(letters.values()))
|
||||||
|
|
||||||
def resolve_connection_letters(self, args, context, info):
|
def resolve_connection_letters(self, info, **args):
|
||||||
return Letter.Connection(
|
return LetterConnection(
|
||||||
page_info=PageInfo(
|
page_info=PageInfo(
|
||||||
has_next_page=True,
|
has_next_page=True,
|
||||||
has_previous_page=False
|
has_previous_page=False
|
||||||
),
|
),
|
||||||
edges=[
|
edges=[
|
||||||
Letter.Connection.Edge(
|
LetterConnection.Edge(
|
||||||
node=Letter(id=0, letter='A'),
|
node=Letter(id=0, letter='A'),
|
||||||
cursor='a-cursor'
|
cursor='a-cursor'
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from ..node import Node, GlobalID
|
from ...types import ID, NonNull, ObjectType, String
|
||||||
from ...types import NonNull, ID, ObjectType, String
|
|
||||||
from ...types.definitions import GrapheneObjectType
|
from ...types.definitions import GrapheneObjectType
|
||||||
|
from ..node import GlobalID, Node
|
||||||
|
|
||||||
|
|
||||||
class CustomNode(Node):
|
class CustomNode(Node):
|
||||||
|
@ -48,7 +48,7 @@ def test_global_id_defaults_to_info_parent_type():
|
||||||
my_id = '1'
|
my_id = '1'
|
||||||
gid = GlobalID()
|
gid = GlobalID()
|
||||||
id_resolver = gid.get_resolver(lambda *_: my_id)
|
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)
|
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'
|
my_id = '1'
|
||||||
gid = GlobalID(parent_type=User)
|
gid = GlobalID(parent_type=User)
|
||||||
id_resolver = gid.get_resolver(lambda *_: my_id)
|
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)
|
assert my_global_id == to_global_id(User._meta.name, my_id)
|
||||||
|
|
|
@ -1,62 +1,86 @@
|
||||||
import pytest
|
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 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()
|
shared = String()
|
||||||
|
|
||||||
|
|
||||||
class MyNode(ObjectType):
|
class MyNode(ObjectType):
|
||||||
class Meta:
|
# class Meta:
|
||||||
interfaces = (Node, )
|
# interfaces = (Node, )
|
||||||
|
id = ID()
|
||||||
name = String()
|
name = String()
|
||||||
|
|
||||||
|
|
||||||
class SaySomething(ClientIDMutation):
|
class SaySomething(ClientIDMutation):
|
||||||
|
|
||||||
class Input:
|
class Input:
|
||||||
what = String()
|
what = String()
|
||||||
|
|
||||||
phrase = String()
|
phrase = String()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate_and_get_payload(args, context, info):
|
def mutate_and_get_payload(self, info, what, client_mutation_id=None):
|
||||||
what = args.get('what')
|
|
||||||
return SaySomething(phrase=str(what))
|
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:
|
class Input:
|
||||||
what = String()
|
what = String()
|
||||||
|
|
||||||
phrase = String()
|
phrase = String()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mutate_and_get_payload(args, context, info):
|
def mutate_and_get_payload(self, info, what, client_mutation_id=None):
|
||||||
what = args.get('what')
|
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)))
|
return Promise.resolve(SaySomething(phrase=str(what)))
|
||||||
|
|
||||||
|
|
||||||
|
# MyEdge = MyNode.Connection.Edge
|
||||||
|
class MyEdge(ObjectType):
|
||||||
|
node = Field(MyNode)
|
||||||
|
cursor = String()
|
||||||
|
|
||||||
|
|
||||||
class OtherMutation(ClientIDMutation):
|
class OtherMutation(ClientIDMutation):
|
||||||
|
|
||||||
class Input(SharedFields):
|
class Input(SharedFields):
|
||||||
additional_field = String()
|
additional_field = String()
|
||||||
|
|
||||||
name = String()
|
name = String()
|
||||||
my_node_edge = Field(MyNode.Connection.Edge)
|
my_node_edge = Field(MyEdge)
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def mutate_and_get_payload(cls, args, context, info):
|
def mutate_and_get_payload(self, info, shared='', additional_field='', client_mutation_id=None):
|
||||||
shared = args.get('shared', '')
|
edge_type = MyEdge
|
||||||
additionalField = args.get('additionalField', '')
|
|
||||||
edge_type = MyNode.Connection.Edge
|
|
||||||
return OtherMutation(
|
return OtherMutation(
|
||||||
name=shared + additionalField,
|
name=shared + additional_field,
|
||||||
my_node_edge=edge_type(cursor='1', node=MyNode(name='name')))
|
my_node_edge=edge_type(cursor='1', node=MyNode(name='name')))
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,6 +90,7 @@ class RootQuery(ObjectType):
|
||||||
|
|
||||||
class Mutation(ObjectType):
|
class Mutation(ObjectType):
|
||||||
say = SaySomething.Field()
|
say = SaySomething.Field()
|
||||||
|
say_fixed = SaySomethingFixed.Field()
|
||||||
say_promise = SaySomethingPromise.Field()
|
say_promise = SaySomethingPromise.Field()
|
||||||
other = OtherMutation.Field()
|
other = OtherMutation.Field()
|
||||||
|
|
||||||
|
@ -86,6 +111,7 @@ def test_no_mutate_and_get_payload():
|
||||||
def test_mutation():
|
def test_mutation():
|
||||||
fields = SaySomething._meta.fields
|
fields = SaySomething._meta.fields
|
||||||
assert list(fields.keys()) == ['phrase', 'client_mutation_id']
|
assert list(fields.keys()) == ['phrase', 'client_mutation_id']
|
||||||
|
assert SaySomething._meta.name == "SaySomethingPayload"
|
||||||
assert isinstance(fields['phrase'], Field)
|
assert isinstance(fields['phrase'], Field)
|
||||||
field = SaySomething.Field()
|
field = SaySomething.Field()
|
||||||
assert field.type == SaySomething
|
assert field.type == SaySomething
|
||||||
|
@ -146,7 +172,14 @@ def test_node_query():
|
||||||
assert executed.data == {'say': {'phrase': 'hello'}}
|
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(
|
executed = schema.execute(
|
||||||
'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }'
|
'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }'
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,12 +2,11 @@ from collections import OrderedDict
|
||||||
|
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from ...types import AbstractType, ObjectType, Schema, String
|
from ...types import ObjectType, Schema, String
|
||||||
from ..connection import Connection
|
from ..node import Node, is_node
|
||||||
from ..node import Node
|
|
||||||
|
|
||||||
|
|
||||||
class SharedNodeFields(AbstractType):
|
class SharedNodeFields(object):
|
||||||
|
|
||||||
shared = String()
|
shared = String()
|
||||||
something_else = String()
|
something_else = String()
|
||||||
|
@ -23,7 +22,7 @@ class MyNode(ObjectType):
|
||||||
name = String()
|
name = String()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node(id, *_):
|
def get_node(info, id):
|
||||||
return MyNode(name=str(id))
|
return MyNode(name=str(id))
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ class MyOtherNode(SharedNodeFields, ObjectType):
|
||||||
return 'extra field info.'
|
return 'extra field info.'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node(id, *_):
|
def get_node(info, id):
|
||||||
return MyOtherNode(shared=str(id))
|
return MyOtherNode(shared=str(id))
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,20 +46,14 @@ class RootQuery(ObjectType):
|
||||||
only_node = Node.Field(MyNode)
|
only_node = Node.Field(MyNode)
|
||||||
only_node_lazy = Node.Field(lambda: MyNode)
|
only_node_lazy = Node.Field(lambda: MyNode)
|
||||||
|
|
||||||
|
|
||||||
schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode])
|
schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode])
|
||||||
|
|
||||||
|
|
||||||
def test_node_good():
|
def test_node_good():
|
||||||
assert 'id' in MyNode._meta.fields
|
assert 'id' in MyNode._meta.fields
|
||||||
|
assert is_node(MyNode)
|
||||||
|
assert not is_node(object)
|
||||||
def test_node_get_connection():
|
|
||||||
connection = MyNode.Connection
|
|
||||||
assert issubclass(connection, Connection)
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_get_connection_dont_duplicate():
|
|
||||||
assert MyNode.Connection == MyNode.Connection
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_query():
|
def test_node_query():
|
||||||
|
@ -80,6 +73,15 @@ def test_subclassed_node_query():
|
||||||
[('shared', '1'), ('extraField', 'extra field info.'), ('somethingElse', '----')])})
|
[('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():
|
def test_node_query_incorrect_id():
|
||||||
executed = schema.execute(
|
executed = schema.execute(
|
||||||
'{ node(id:"%s") { ... on MyNode { name } } }' % "something:2"
|
'{ node(id:"%s") { ... on MyNode { name } } }' % "something:2"
|
||||||
|
@ -114,7 +116,7 @@ def test_node_field_only_type_wrong():
|
||||||
)
|
)
|
||||||
assert len(executed.errors) == 1
|
assert len(executed.errors) == 1
|
||||||
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.'
|
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():
|
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 len(executed.errors) == 1
|
||||||
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.'
|
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.'
|
||||||
assert executed.data == { 'onlyNodeLazy': None }
|
assert executed.data == {'onlyNodeLazy': None}
|
||||||
|
|
||||||
|
|
||||||
def test_str_schema():
|
def test_str_schema():
|
||||||
|
|
|
@ -15,7 +15,7 @@ class CustomNode(Node):
|
||||||
return id
|
return id
|
||||||
|
|
||||||
@staticmethod
|
@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
|
assert info.schema == schema
|
||||||
if id in user_data:
|
if id in user_data:
|
||||||
return user_data.get(id)
|
return user_data.get(id)
|
||||||
|
|
|
@ -29,6 +29,7 @@ def format_execution_result(execution_result, format_error):
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
|
|
||||||
def __init__(self, schema, format_error=None, **execute_options):
|
def __init__(self, schema, format_error=None, **execute_options):
|
||||||
assert isinstance(schema, Schema)
|
assert isinstance(schema, Schema)
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import resolve_only_args
|
from graphene import resolve_only_args
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
rand = graphene.String()
|
rand = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
class Success(graphene.ObjectType):
|
class Success(graphene.ObjectType):
|
||||||
yeah = graphene.String()
|
yeah = graphene.String()
|
||||||
|
|
||||||
|
@ -15,18 +17,19 @@ class Error(graphene.ObjectType):
|
||||||
|
|
||||||
|
|
||||||
class CreatePostResult(graphene.Union):
|
class CreatePostResult(graphene.Union):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
types = [Success, Error]
|
types = [Success, Error]
|
||||||
|
|
||||||
|
|
||||||
class CreatePost(graphene.Mutation):
|
class CreatePost(graphene.Mutation):
|
||||||
|
|
||||||
class Input:
|
class Input:
|
||||||
text = graphene.String(required=True)
|
text = graphene.String(required=True)
|
||||||
|
|
||||||
result = graphene.Field(CreatePostResult)
|
result = graphene.Field(CreatePostResult)
|
||||||
|
|
||||||
@resolve_only_args
|
def mutate(self, info, text):
|
||||||
def mutate(self, text):
|
|
||||||
result = Success(yeah='yeah')
|
result = Success(yeah='yeah')
|
||||||
|
|
||||||
return CreatePost(result=result)
|
return CreatePost(result=result)
|
||||||
|
@ -37,6 +40,7 @@ class Mutations(graphene.ObjectType):
|
||||||
|
|
||||||
# tests.py
|
# tests.py
|
||||||
|
|
||||||
|
|
||||||
def test_create_post():
|
def test_create_post():
|
||||||
query_string = '''
|
query_string = '''
|
||||||
mutation {
|
mutation {
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
# https://github.com/graphql-python/graphene/issues/356
|
# https://github.com/graphql-python/graphene/issues/356
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
|
|
||||||
|
|
||||||
class SomeTypeOne(graphene.ObjectType):
|
class SomeTypeOne(graphene.ObjectType):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SomeTypeTwo(graphene.ObjectType):
|
class SomeTypeTwo(graphene.ObjectType):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MyUnion(graphene.Union):
|
class MyUnion(graphene.Union):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
types = (SomeTypeOne, SomeTypeTwo)
|
types = (SomeTypeOne, SomeTypeTwo)
|
||||||
|
|
||||||
|
|
||||||
def test_issue():
|
def test_issue():
|
||||||
with pytest.raises(Exception) as exc_info:
|
with pytest.raises(Exception) as exc_info:
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
|
|
@ -1,36 +1,25 @@
|
||||||
# https://github.com/graphql-python/graphene/issues/425
|
# 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 ObjectType, ObjectTypeOptions
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class SpecialObjectType(six.with_metaclass(SpecialObjectTypeMeta, ObjectType)):
|
class SpecialOptions(ObjectTypeOptions):
|
||||||
pass
|
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():
|
def test_special_objecttype_could_be_subclassed():
|
||||||
class MyType(SpecialObjectType):
|
class MyType(SpecialObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
other_attr = 'yeah!'
|
other_attr = 'yeah!'
|
||||||
|
|
||||||
|
@ -49,5 +38,5 @@ def test_special_objecttype_inherit_meta_options():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert MyType._meta.name == 'MyType'
|
assert MyType._meta.name == 'MyType'
|
||||||
assert MyType._meta.default_resolver == None
|
assert MyType._meta.default_resolver is None
|
||||||
assert MyType._meta.interfaces == ()
|
assert MyType._meta.interfaces == ()
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
# https://github.com/graphql-python/graphene/issues/313
|
# https://github.com/graphql-python/graphene/issues/313
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import resolve_only_args
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
some_field = graphene.String(from_=graphene.String(name="from"))
|
some_field = graphene.String(from_=graphene.String(name="from"))
|
||||||
|
|
||||||
def resolve_some_field(_, args, context, infos):
|
def resolve_some_field(self, info, from_=None):
|
||||||
return args.get("from_")
|
return from_
|
||||||
|
|
||||||
|
|
||||||
def test_issue():
|
def test_issue():
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
from graphql import ResolveInfo
|
||||||
|
|
||||||
from .objecttype import ObjectType
|
from .objecttype import ObjectType
|
||||||
from .abstracttype import AbstractType
|
|
||||||
from .interface import Interface
|
from .interface import Interface
|
||||||
from .mutation import Mutation
|
from .mutation import Mutation
|
||||||
from .scalars import Scalar, String, ID, Int, Float, Boolean
|
from .scalars import Scalar, String, ID, Int, Float, Boolean
|
||||||
|
from .json import JSONString
|
||||||
|
from .uuid import UUID
|
||||||
from .schema import Schema
|
from .schema import Schema
|
||||||
from .structures import List, NonNull
|
from .structures import List, NonNull
|
||||||
from .enum import Enum
|
from .enum import Enum
|
||||||
|
@ -14,10 +16,13 @@ from .argument import Argument
|
||||||
from .inputobjecttype import InputObjectType
|
from .inputobjecttype import InputObjectType
|
||||||
from .dynamic import Dynamic
|
from .dynamic import Dynamic
|
||||||
from .union import Union
|
from .union import Union
|
||||||
|
from .context import Context
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
from .abstracttype import AbstractType
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AbstractType',
|
|
||||||
'ObjectType',
|
'ObjectType',
|
||||||
'InputObjectType',
|
'InputObjectType',
|
||||||
'Interface',
|
'Interface',
|
||||||
|
@ -31,10 +36,17 @@ __all__ = [
|
||||||
'ID',
|
'ID',
|
||||||
'Int',
|
'Int',
|
||||||
'Float',
|
'Float',
|
||||||
|
'JSONString',
|
||||||
|
'UUID',
|
||||||
'Boolean',
|
'Boolean',
|
||||||
'List',
|
'List',
|
||||||
'NonNull',
|
'NonNull',
|
||||||
'Argument',
|
'Argument',
|
||||||
'Dynamic',
|
'Dynamic',
|
||||||
'Union',
|
'Union',
|
||||||
|
'Context',
|
||||||
|
'ResolveInfo',
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
'AbstractType',
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,41 +1,12 @@
|
||||||
import six
|
from ..utils.subclass_with_meta import SubclassWithMeta
|
||||||
|
from ..utils.deprecated import warn_deprecation
|
||||||
from ..utils.is_base_type import is_base_type
|
|
||||||
from .options import Options
|
|
||||||
from .utils import get_base_fields, merge, yank_fields_from_attrs
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractTypeMeta(type):
|
class AbstractType(SubclassWithMeta):
|
||||||
'''
|
|
||||||
AbstractType Definition
|
|
||||||
|
|
||||||
When we want to share fields across multiple types, like a Interface,
|
def __init_subclass__(cls, *args, **kwargs):
|
||||||
a ObjectType and a Input ObjectType we can use AbstractTypes for defining
|
warn_deprecation(
|
||||||
our fields that the other types will inherit from.
|
"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"
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
|
super(AbstractType, cls).__init_subclass__(*args, **kwargs)
|
||||||
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractType(six.with_metaclass(AbstractTypeMeta)):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
|
from .dynamic import Dynamic
|
||||||
from .mountedtype import MountedType
|
from .mountedtype import MountedType
|
||||||
from .structures import NonNull
|
from .structures import NonNull
|
||||||
from .dynamic import Dynamic
|
|
||||||
from .utils import get_type
|
from .utils import get_type
|
||||||
|
|
||||||
|
|
||||||
|
|
42
graphene/types/base.py
Normal file
42
graphene/types/base.py
Normal 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__()
|
4
graphene/types/context.py
Normal file
4
graphene/types/context.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
class Context(object):
|
||||||
|
def __init__(self, **params):
|
||||||
|
for key, value in params.items():
|
||||||
|
setattr(self, key, value)
|
|
@ -2,15 +2,12 @@ from collections import OrderedDict
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ..utils.is_base_type import is_base_type
|
from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta
|
||||||
from ..utils.trim_docstring import trim_docstring
|
|
||||||
from .options import Options
|
from .base import BaseOptions, BaseType
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
|
|
||||||
try:
|
from ..pyutils.compat import Enum as PyEnum
|
||||||
from enum import Enum as PyEnum
|
|
||||||
except ImportError:
|
|
||||||
from ..pyutils.enum import Enum as PyEnum
|
|
||||||
|
|
||||||
|
|
||||||
def eq_enum(self, other):
|
def eq_enum(self, other):
|
||||||
|
@ -19,29 +16,18 @@ def eq_enum(self, other):
|
||||||
return self.value is 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(
|
class EnumOptions(BaseOptions):
|
||||||
attrs.pop('Meta', None),
|
enum = None # type: Enum
|
||||||
name=name,
|
|
||||||
description=trim_docstring(attrs.get('__doc__')),
|
|
||||||
enum=None,
|
|
||||||
)
|
|
||||||
if not options.enum:
|
|
||||||
attrs['__eq__'] = eq_enum
|
|
||||||
options.enum = PyEnum(cls.__name__, attrs)
|
|
||||||
|
|
||||||
new_attrs = OrderedDict(attrs, _meta=options, **options.enum.__members__)
|
|
||||||
return type.__new__(cls, name, bases, new_attrs)
|
|
||||||
|
|
||||||
def __prepare__(name, bases, **kwargs): # noqa: N805
|
class EnumMeta(SubclassWithMeta_Meta):
|
||||||
return OrderedDict()
|
|
||||||
|
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):
|
def get(cls, value):
|
||||||
return cls._meta.enum(value)
|
return cls._meta.enum(value)
|
||||||
|
@ -49,29 +35,30 @@ class EnumTypeMeta(type):
|
||||||
def __getitem__(cls, value):
|
def __getitem__(cls, value):
|
||||||
return cls._meta.enum[value]
|
return cls._meta.enum[value]
|
||||||
|
|
||||||
|
def __prepare__(name, bases, **kwargs): # noqa: N805
|
||||||
|
return OrderedDict()
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs): # noqa: N805
|
def __call__(cls, *args, **kwargs): # noqa: N805
|
||||||
if cls is Enum:
|
if cls is Enum:
|
||||||
description = kwargs.pop('description', None)
|
description = kwargs.pop('description', None)
|
||||||
return cls.from_enum(PyEnum(*args, **kwargs), description=description)
|
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)
|
# return cls._meta.enum(*args, **kwargs)
|
||||||
|
|
||||||
def from_enum(cls, enum, description=None): # noqa: N805
|
def from_enum(cls, enum, description=None): # noqa: N805
|
||||||
meta_class = type('Meta', (object,), {'enum': enum, 'description': description})
|
meta_class = type('Meta', (object,), {'enum': enum, 'description': description})
|
||||||
return type(meta_class.enum.__name__, (Enum,), {'Meta': meta_class})
|
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)):
|
@classmethod
|
||||||
'''
|
def __init_subclass_with_meta__(cls, enum=None, **options):
|
||||||
Enum Type Definition
|
_meta = EnumOptions(cls)
|
||||||
|
_meta.enum = enum or cls.__enum__
|
||||||
Some leaf values of requests and input values are Enums. GraphQL serializes
|
for key, value in _meta.enum.__members__.items():
|
||||||
Enum values as strings, however internally Enums can be represented by any
|
setattr(cls, key, value)
|
||||||
kind of type, often integers.
|
super(Enum, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||||
'''
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
|
|
|
@ -8,11 +8,10 @@ from .structures import NonNull
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
from .utils import get_type
|
from .utils import get_type
|
||||||
|
|
||||||
|
|
||||||
base_type = type
|
base_type = type
|
||||||
|
|
||||||
|
|
||||||
def source_resolver(source, root, args, context, info):
|
def source_resolver(source, root, info, **args):
|
||||||
resolved = getattr(root, source, None)
|
resolved = getattr(root, source, None)
|
||||||
if inspect.isfunction(resolved) or inspect.ismethod(resolved):
|
if inspect.isfunction(resolved) or inspect.ismethod(resolved):
|
||||||
return resolved()
|
return resolved()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from graphene.types.scalars import MAX_INT, MIN_INT
|
||||||
from graphql.language.ast import (BooleanValue, FloatValue, IntValue,
|
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
|
from .scalars import Scalar
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,45 +1,36 @@
|
||||||
import six
|
from collections import OrderedDict
|
||||||
|
|
||||||
from ..utils.is_base_type import is_base_type
|
from .base import BaseOptions, BaseType
|
||||||
from ..utils.trim_docstring import trim_docstring
|
|
||||||
from .abstracttype import AbstractTypeMeta
|
|
||||||
from .inputfield import InputField
|
from .inputfield import InputField
|
||||||
from .options import Options
|
|
||||||
from .unmountedtype import UnmountedType
|
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):
|
# For static type checking with Mypy
|
||||||
|
MYPY = False
|
||||||
def __new__(cls, name, bases, attrs):
|
if MYPY:
|
||||||
# Also ensure initialization is only performed for subclasses of
|
from typing import Dict, Callable # NOQA
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
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
|
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
|
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
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -1,45 +1,20 @@
|
||||||
import six
|
from collections import OrderedDict
|
||||||
|
|
||||||
from ..utils.is_base_type import is_base_type
|
from .base import BaseOptions, BaseType
|
||||||
from ..utils.trim_docstring import trim_docstring
|
|
||||||
from .abstracttype import AbstractTypeMeta
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
from .options import Options
|
from .utils import yank_fields_from_attrs
|
||||||
from .utils import get_base_fields, merge, yank_fields_from_attrs
|
|
||||||
|
# For static type checking with Mypy
|
||||||
|
MYPY = False
|
||||||
|
if MYPY:
|
||||||
|
from typing import Dict # NOQA
|
||||||
|
|
||||||
|
|
||||||
class InterfaceMeta(AbstractTypeMeta):
|
class InterfaceOptions(BaseOptions):
|
||||||
|
fields = None # type: Dict[str, Field]
|
||||||
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 Interface(six.with_metaclass(InterfaceMeta)):
|
class Interface(BaseType):
|
||||||
'''
|
'''
|
||||||
Interface Type Definition
|
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
|
all types, as well as a function to determine which type is actually used
|
||||||
when the field is resolved.
|
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
|
@classmethod
|
||||||
def resolve_type(cls, instance, context, info):
|
def resolve_type(cls, instance, info):
|
||||||
from .objecttype import ObjectType
|
from .objecttype import ObjectType
|
||||||
if isinstance(instance, ObjectType):
|
if isinstance(instance, ObjectType):
|
||||||
return type(instance)
|
return type(instance)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
raise Exception("An Interface cannot be intitialized")
|
raise Exception("An Interface cannot be intitialized")
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def implements(cls, objecttype):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -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.get_unbound_function import get_unbound_function
|
||||||
from ..utils.props import props
|
from ..utils.props import props
|
||||||
from .field import Field
|
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):
|
# For static type checking with Mypy
|
||||||
def __new__(cls, name, bases, attrs):
|
MYPY = False
|
||||||
# Also ensure initialization is only performed for subclasses of
|
if MYPY:
|
||||||
# Mutation
|
from .argument import Argument # NOQA
|
||||||
if not is_base_type(bases, MutationMeta):
|
from typing import Dict, Type, Callable # NOQA
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class Mutation(six.with_metaclass(MutationMeta, ObjectType)):
|
class MutationOptions(ObjectTypeOptions):
|
||||||
pass
|
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
|
||||||
|
)
|
||||||
|
|
|
@ -1,82 +1,64 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import six
|
from .base import BaseOptions, BaseType
|
||||||
|
|
||||||
from ..utils.is_base_type import is_base_type
|
|
||||||
from ..utils.trim_docstring import trim_docstring
|
|
||||||
from .abstracttype import AbstractTypeMeta
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
from .interface import Interface
|
from .interface import Interface
|
||||||
from .options import Options
|
from .utils import yank_fields_from_attrs
|
||||||
from .utils import get_base_fields, merge, yank_fields_from_attrs
|
|
||||||
|
# For static type checking with Mypy
|
||||||
|
MYPY = False
|
||||||
|
if MYPY:
|
||||||
|
from typing import Dict, Iterable, Type # NOQA
|
||||||
|
|
||||||
|
|
||||||
class ObjectTypeMeta(AbstractTypeMeta):
|
class ObjectTypeOptions(BaseOptions):
|
||||||
|
fields = None # type: Dict[str, Field]
|
||||||
def __new__(cls, name, bases, attrs):
|
interfaces = () # type: Iterable[Type[Interface]]
|
||||||
# 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 ObjectType(six.with_metaclass(ObjectTypeMeta)):
|
class ObjectType(BaseType):
|
||||||
'''
|
'''
|
||||||
Object Type Definition
|
Object Type Definition
|
||||||
|
|
||||||
Almost all of the GraphQL types you define will be object types. Object types
|
Almost all of the GraphQL types you define will be object types. Object types
|
||||||
have a name, but most importantly describe their fields.
|
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
|
is_type_of = None
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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)
|
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)
|
return root.get(attname, default_value)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,17 @@
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from graphql.language.ast import (BooleanValue, FloatValue, IntValue,
|
from graphql.language.ast import (BooleanValue, FloatValue, IntValue,
|
||||||
StringValue)
|
StringValue)
|
||||||
|
|
||||||
from ..utils.is_base_type import is_base_type
|
from .base import BaseOptions, BaseType
|
||||||
from ..utils.trim_docstring import trim_docstring
|
|
||||||
from .options import Options
|
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
|
|
||||||
|
|
||||||
class ScalarTypeMeta(type):
|
class ScalarOptions(BaseOptions):
|
||||||
|
pass
|
||||||
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 Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
|
class Scalar(UnmountedType, BaseType):
|
||||||
'''
|
'''
|
||||||
Scalar Type Definition
|
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
|
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.
|
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
|
serialize = None
|
||||||
parse_value = None
|
parse_value = None
|
||||||
|
@ -99,6 +86,7 @@ class Float(Scalar):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def coerce_float(value):
|
def coerce_float(value):
|
||||||
|
# type: (Any) -> float
|
||||||
try:
|
try:
|
||||||
return float(value)
|
return float(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
|
@ -68,9 +68,9 @@ class NonNull(Structure):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(NonNull, self).__init__(*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: {}.'
|
'Can only create NonNull of a Nullable GraphQLType but got: {}.'
|
||||||
).format(self.of_type)
|
).format(self._of_type)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}!'.format(self.of_type)
|
return '{}!'.format(self.of_type)
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from ..abstracttype import AbstractType
|
from ..objecttype import ObjectType
|
||||||
from ..field import Field
|
|
||||||
from ..unmountedtype import UnmountedType
|
from ..unmountedtype import UnmountedType
|
||||||
|
from ..abstracttype import AbstractType
|
||||||
|
from .. import abstracttype
|
||||||
|
from ..field import Field
|
||||||
|
|
||||||
|
|
||||||
class MyType(object):
|
class MyType(ObjectType):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,29 +17,24 @@ class MyScalar(UnmountedType):
|
||||||
return MyType
|
return MyType
|
||||||
|
|
||||||
|
|
||||||
def test_generate_abstracttype_with_fields():
|
def test_abstract_objecttype_warn_deprecation(mocker):
|
||||||
|
mocker.patch.object(abstracttype, 'warn_deprecation')
|
||||||
|
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(AbstractType):
|
||||||
field = Field(MyType)
|
field1 = MyScalar()
|
||||||
|
|
||||||
assert 'field' in MyAbstractType._meta.fields
|
assert abstracttype.warn_deprecation.called
|
||||||
assert isinstance(MyAbstractType._meta.fields['field'], Field)
|
|
||||||
|
|
||||||
|
|
||||||
def test_generate_abstracttype_with_unmountedfields():
|
def test_generate_objecttype_inherit_abstracttype():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(AbstractType):
|
||||||
field = UnmountedType(MyType)
|
field1 = MyScalar()
|
||||||
|
|
||||||
assert 'field' in MyAbstractType._meta.fields
|
class MyObjectType(ObjectType, MyAbstractType):
|
||||||
assert isinstance(MyAbstractType._meta.fields['field'], UnmountedType)
|
field2 = MyScalar()
|
||||||
|
|
||||||
|
assert MyObjectType._meta.description is None
|
||||||
def test_generate_abstracttype_inheritance():
|
assert MyObjectType._meta.interfaces == ()
|
||||||
class MyAbstractType1(AbstractType):
|
assert MyObjectType._meta.name == "MyObjectType"
|
||||||
field1 = UnmountedType(MyType)
|
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||||
|
assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field]
|
||||||
class MyAbstractType2(MyAbstractType1):
|
|
||||||
field2 = UnmountedType(MyType)
|
|
||||||
|
|
||||||
assert list(MyAbstractType2._meta.fields.keys()) == ['field1', 'field2']
|
|
||||||
assert not hasattr(MyAbstractType1, 'field1')
|
|
||||||
assert not hasattr(MyAbstractType2, 'field2')
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import pytest
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from ..argument import Argument, to_arguments
|
from ..argument import Argument, to_arguments
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
from ..structures import NonNull
|
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
|
from ..structures import NonNull
|
||||||
|
|
||||||
|
|
||||||
def test_argument():
|
def test_argument():
|
||||||
|
|
63
graphene/types/tests/test_base.py
Normal file
63
graphene/types/tests/test_base.py
Normal 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"
|
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from ..datetime import DateTime, Time
|
from ..datetime import DateTime, Time
|
||||||
|
@ -10,12 +11,11 @@ class Query(ObjectType):
|
||||||
datetime = DateTime(_in=DateTime(name='in'))
|
datetime = DateTime(_in=DateTime(name='in'))
|
||||||
time = Time(_at=Time(name='at'))
|
time = Time(_at=Time(name='at'))
|
||||||
|
|
||||||
def resolve_datetime(self, args, context, info):
|
def resolve_datetime(self, info, _in=None):
|
||||||
_in = args.get('_in')
|
|
||||||
return _in
|
return _in
|
||||||
|
|
||||||
def resolve_time(self, args, context, info):
|
def resolve_time(self, info, _at=None):
|
||||||
return args.get('_at')
|
return _at
|
||||||
|
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
schema = Schema(query=Query)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
from ..abstracttype import AbstractType
|
|
||||||
from ..argument import Argument
|
from ..argument import Argument
|
||||||
from ..enum import Enum
|
from ..enum import Enum
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
|
@ -296,7 +295,7 @@ def test_stringifies_simple_types():
|
||||||
|
|
||||||
|
|
||||||
def test_does_not_mutate_passed_field_definitions():
|
def test_does_not_mutate_passed_field_definitions():
|
||||||
class CommonFields(AbstractType):
|
class CommonFields(object):
|
||||||
field1 = String()
|
field1 = String()
|
||||||
field2 = String(id=String())
|
field2 = String(id=String())
|
||||||
|
|
||||||
|
@ -307,12 +306,8 @@ def test_does_not_mutate_passed_field_definitions():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert TestObject1._meta.fields == TestObject2._meta.fields
|
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()
|
field1 = String()
|
||||||
field2 = String()
|
field2 = String()
|
||||||
|
|
||||||
|
@ -323,8 +318,3 @@ def test_does_not_mutate_passed_field_definitions():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert TestInputObject1._meta.fields == TestInputObject2._meta.fields
|
assert TestInputObject1._meta.fields == TestInputObject2._meta.fields
|
||||||
|
|
||||||
assert CommonFields._meta.fields == {
|
|
||||||
'field1': String(),
|
|
||||||
'field2': String(),
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from ..structures import List, NonNull
|
|
||||||
from ..scalars import String
|
|
||||||
from ..dynamic import Dynamic
|
from ..dynamic import Dynamic
|
||||||
|
from ..scalars import String
|
||||||
|
from ..structures import List, NonNull
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic():
|
def test_dynamic():
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
from ..argument import Argument
|
||||||
from ..enum import Enum, PyEnum
|
from ..enum import Enum, PyEnum
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
from ..argument import Argument
|
|
||||||
|
|
||||||
|
|
||||||
def test_enum_construction():
|
def test_enum_construction():
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import pytest
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from ..argument import Argument
|
from ..argument import Argument
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..structures import NonNull
|
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
|
from ..structures import NonNull
|
||||||
from .utils import MyLazyType
|
from .utils import MyLazyType
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,10 +20,11 @@ class MyInstance(object):
|
||||||
def test_field_basic():
|
def test_field_basic():
|
||||||
MyType = object()
|
MyType = object()
|
||||||
args = {'my arg': Argument(True)}
|
args = {'my arg': Argument(True)}
|
||||||
resolver = lambda: None
|
|
||||||
|
def resolver(): return None
|
||||||
deprecation_reason = 'Deprecated now'
|
deprecation_reason = 'Deprecated now'
|
||||||
description = 'My Field'
|
description = 'My Field'
|
||||||
my_default='something'
|
my_default = 'something'
|
||||||
field = Field(
|
field = Field(
|
||||||
MyType,
|
MyType,
|
||||||
name='name',
|
name='name',
|
||||||
|
@ -59,7 +61,7 @@ def test_field_default_value_not_callable():
|
||||||
def test_field_source():
|
def test_field_source():
|
||||||
MyType = object()
|
MyType = object()
|
||||||
field = Field(MyType, source='value')
|
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():
|
def test_field_with_lazy_type():
|
||||||
|
@ -83,19 +85,20 @@ def test_field_not_source_and_resolver():
|
||||||
MyType = object()
|
MyType = object()
|
||||||
with pytest.raises(Exception) as exc_info:
|
with pytest.raises(Exception) as exc_info:
|
||||||
Field(MyType, source='value', resolver=lambda: None)
|
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():
|
def test_field_source_func():
|
||||||
MyType = object()
|
MyType = object()
|
||||||
field = Field(MyType, source='value_func')
|
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():
|
def test_field_source_method():
|
||||||
MyType = object()
|
MyType = object()
|
||||||
field = Field(MyType, source='value_method')
|
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():
|
def test_field_source_as_argument():
|
||||||
|
|
|
@ -6,8 +6,7 @@ from ..schema import Schema
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
generic = GenericScalar(input=GenericScalar())
|
generic = GenericScalar(input=GenericScalar())
|
||||||
|
|
||||||
def resolve_generic(self, args, context, info):
|
def resolve_generic(self, info, input=None):
|
||||||
input = args.get('input')
|
|
||||||
return input
|
return input
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import pytest
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
|
||||||
from ..abstracttype import AbstractType
|
|
||||||
from ..field import Field
|
|
||||||
from ..argument import Argument
|
from ..argument import Argument
|
||||||
|
from ..field import Field
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
from ..objecttype import ObjectType
|
|
||||||
from ..inputobjecttype import InputObjectType
|
from ..inputobjecttype import InputObjectType
|
||||||
|
from ..objecttype import ObjectType
|
||||||
from ..unmountedtype import UnmountedType
|
from ..unmountedtype import UnmountedType
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,7 +79,7 @@ def test_generate_inputobjecttype_as_argument():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_inputobjecttype_inherit_abstracttype():
|
def test_generate_inputobjecttype_inherit_abstracttype():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(object):
|
||||||
field1 = MyScalar(MyType)
|
field1 = MyScalar(MyType)
|
||||||
|
|
||||||
class MyInputObjectType(InputObjectType, MyAbstractType):
|
class MyInputObjectType(InputObjectType, MyAbstractType):
|
||||||
|
@ -91,7 +90,7 @@ def test_generate_inputobjecttype_inherit_abstracttype():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
|
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(object):
|
||||||
field1 = MyScalar(MyType)
|
field1 = MyScalar(MyType)
|
||||||
|
|
||||||
class MyInputObjectType(MyAbstractType, InputObjectType):
|
class MyInputObjectType(MyAbstractType, InputObjectType):
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
from ..abstracttype import AbstractType
|
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..interface import Interface
|
from ..interface import Interface
|
||||||
from ..unmountedtype import UnmountedType
|
from ..unmountedtype import UnmountedType
|
||||||
|
@ -61,7 +59,7 @@ def test_generate_interface_unmountedtype():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_interface_inherit_abstracttype():
|
def test_generate_interface_inherit_abstracttype():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(object):
|
||||||
field1 = MyScalar()
|
field1 = MyScalar()
|
||||||
|
|
||||||
class MyInterface(Interface, MyAbstractType):
|
class MyInterface(Interface, MyAbstractType):
|
||||||
|
@ -84,7 +82,7 @@ def test_generate_interface_inherit_interface():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_interface_inherit_abstracttype_reversed():
|
def test_generate_interface_inherit_abstracttype_reversed():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(object):
|
||||||
field1 = MyScalar()
|
field1 = MyScalar()
|
||||||
|
|
||||||
class MyInterface(MyAbstractType, Interface):
|
class MyInterface(MyAbstractType, Interface):
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from ..json import JSONString
|
from ..json import JSONString
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
|
@ -8,8 +7,7 @@ from ..schema import Schema
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
json = JSONString(input=JSONString())
|
json = JSONString(input=JSONString())
|
||||||
|
|
||||||
def resolve_json(self, args, context, info):
|
def resolve_json(self, info, input):
|
||||||
input = args.get('input')
|
|
||||||
return input
|
return input
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
schema = Schema(query=Query)
|
||||||
|
@ -19,7 +17,7 @@ def test_jsonstring_query():
|
||||||
json_value = '{"key": "value"}'
|
json_value = '{"key": "value"}'
|
||||||
|
|
||||||
json_value_quoted = json_value.replace('"', '\\"')
|
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 not result.errors
|
||||||
assert result.data == {
|
assert result.data == {
|
||||||
'json': json_value
|
'json': json_value
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from ..mountedtype import MountedType
|
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
|
|
||||||
|
|
||||||
class CustomField(Field):
|
class CustomField(Field):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.metadata = kwargs.pop('metadata', None)
|
self.metadata = kwargs.pop('metadata', None)
|
||||||
super(CustomField, self).__init__(*args, **kwargs)
|
super(CustomField, self).__init__(*args, **kwargs)
|
||||||
|
|
|
@ -1,40 +1,41 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from ..argument import Argument
|
||||||
|
from ..dynamic import Dynamic
|
||||||
from ..mutation import Mutation
|
from ..mutation import Mutation
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..schema import Schema
|
|
||||||
from ..argument import Argument
|
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
from ..dynamic import Dynamic
|
from ..schema import Schema
|
||||||
|
|
||||||
|
|
||||||
def test_generate_mutation_no_args():
|
def test_generate_mutation_no_args():
|
||||||
class MyMutation(Mutation):
|
class MyMutation(Mutation):
|
||||||
'''Documentation'''
|
'''Documentation'''
|
||||||
|
|
||||||
@classmethod
|
def mutate(self, info, **args):
|
||||||
def mutate(cls, *args, **kwargs):
|
return args
|
||||||
pass
|
|
||||||
|
|
||||||
assert issubclass(MyMutation, ObjectType)
|
assert issubclass(MyMutation, ObjectType)
|
||||||
assert MyMutation._meta.name == "MyMutation"
|
assert MyMutation._meta.name == "MyMutation"
|
||||||
assert MyMutation._meta.description == "Documentation"
|
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():
|
def test_generate_mutation_with_meta():
|
||||||
class MyMutation(Mutation):
|
class MyMutation(Mutation):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
name = 'MyOtherMutation'
|
name = 'MyOtherMutation'
|
||||||
description = 'Documentation'
|
description = 'Documentation'
|
||||||
|
|
||||||
@classmethod
|
def mutate(self, info, **args):
|
||||||
def mutate(cls, *args, **kwargs):
|
return args
|
||||||
pass
|
|
||||||
|
|
||||||
assert MyMutation._meta.name == "MyOtherMutation"
|
assert MyMutation._meta.name == "MyOtherMutation"
|
||||||
assert MyMutation._meta.description == "Documentation"
|
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():
|
def test_mutation_raises_exception_if_no_mutate():
|
||||||
|
@ -52,24 +53,26 @@ def test_mutation_custom_output_type():
|
||||||
name = String()
|
name = String()
|
||||||
|
|
||||||
class CreateUser(Mutation):
|
class CreateUser(Mutation):
|
||||||
|
|
||||||
class Input:
|
class Input:
|
||||||
name = String()
|
name = String()
|
||||||
|
|
||||||
Output = User
|
Output = User
|
||||||
|
|
||||||
@classmethod
|
def mutate(self, info, name):
|
||||||
def mutate(cls, args, context, info):
|
|
||||||
name = args.get('name')
|
|
||||||
return User(name=name)
|
return User(name=name)
|
||||||
|
|
||||||
field = CreateUser.Field()
|
field = CreateUser.Field()
|
||||||
assert field.type == User
|
assert field.type == User
|
||||||
assert field.args == {'name': Argument(String)}
|
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():
|
def test_mutation_execution():
|
||||||
class CreateUser(Mutation):
|
class CreateUser(Mutation):
|
||||||
|
|
||||||
class Input:
|
class Input:
|
||||||
name = String()
|
name = String()
|
||||||
dynamic = Dynamic(lambda: String())
|
dynamic = Dynamic(lambda: String())
|
||||||
|
@ -78,9 +81,7 @@ def test_mutation_execution():
|
||||||
name = String()
|
name = String()
|
||||||
dynamic = Dynamic(lambda: String())
|
dynamic = Dynamic(lambda: String())
|
||||||
|
|
||||||
def mutate(self, args, context, info):
|
def mutate(self, info, name, dynamic):
|
||||||
name = args.get('name')
|
|
||||||
dynamic = args.get('dynamic')
|
|
||||||
return CreateUser(name=name, dynamic=dynamic)
|
return CreateUser(name=name, dynamic=dynamic)
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..abstracttype import AbstractType
|
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..interface import Interface
|
from ..interface import Interface
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
|
@ -89,7 +88,7 @@ def test_ordered_fields_in_objecttype():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_objecttype_inherit_abstracttype():
|
def test_generate_objecttype_inherit_abstracttype():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(object):
|
||||||
field1 = MyScalar()
|
field1 = MyScalar()
|
||||||
|
|
||||||
class MyObjectType(ObjectType, MyAbstractType):
|
class MyObjectType(ObjectType, MyAbstractType):
|
||||||
|
@ -103,7 +102,7 @@ def test_generate_objecttype_inherit_abstracttype():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_objecttype_inherit_abstracttype_reversed():
|
def test_generate_objecttype_inherit_abstracttype_reversed():
|
||||||
class MyAbstractType(AbstractType):
|
class MyAbstractType(object):
|
||||||
field1 = MyScalar()
|
field1 = MyScalar()
|
||||||
|
|
||||||
class MyObjectType(MyAbstractType, ObjectType):
|
class MyObjectType(MyAbstractType, ObjectType):
|
||||||
|
@ -188,6 +187,7 @@ def test_generate_objecttype_description():
|
||||||
|
|
||||||
def test_objecttype_with_possible_types():
|
def test_objecttype_with_possible_types():
|
||||||
class MyObjectType(ObjectType):
|
class MyObjectType(ObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
possible_types = (dict, )
|
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():
|
def test_objecttype_with_possible_types_and_is_type_of_should_raise():
|
||||||
with pytest.raises(AssertionError) as excinfo:
|
with pytest.raises(AssertionError) as excinfo:
|
||||||
class MyObjectType(ObjectType):
|
class MyObjectType(ObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
possible_types = (dict, )
|
possible_types = (dict, )
|
||||||
|
|
||||||
|
|
|
@ -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>'
|
|
|
@ -1,18 +1,19 @@
|
||||||
import json
|
import json
|
||||||
from functools import partial
|
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 ..field import Field
|
||||||
from ..interface import Interface
|
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
from ..inputobjecttype import InputObjectType
|
from ..inputobjecttype import InputObjectType
|
||||||
|
from ..interface import Interface
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..scalars import Int, String
|
from ..scalars import Int, String
|
||||||
from ..schema import Schema
|
from ..schema import Schema
|
||||||
from ..structures import List
|
from ..structures import List
|
||||||
from ..union import Union
|
from ..union import Union
|
||||||
from ..dynamic import Dynamic
|
from ..context import Context
|
||||||
|
|
||||||
|
|
||||||
def test_query():
|
def test_query():
|
||||||
|
@ -26,6 +27,23 @@ def test_query():
|
||||||
assert executed.data == {'hello': 'World'}
|
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():
|
def test_query_union():
|
||||||
class one_object(object):
|
class one_object(object):
|
||||||
pass
|
pass
|
||||||
|
@ -37,24 +55,25 @@ def test_query_union():
|
||||||
one = String()
|
one = String()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_type_of(cls, root, context, info):
|
def is_type_of(cls, root, info):
|
||||||
return isinstance(root, one_object)
|
return isinstance(root, one_object)
|
||||||
|
|
||||||
class Two(ObjectType):
|
class Two(ObjectType):
|
||||||
two = String()
|
two = String()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_type_of(cls, root, context, info):
|
def is_type_of(cls, root, info):
|
||||||
return isinstance(root, two_object)
|
return isinstance(root, two_object)
|
||||||
|
|
||||||
class MyUnion(Union):
|
class MyUnion(Union):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
types = (One, Two)
|
types = (One, Two)
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
unions = List(MyUnion)
|
unions = List(MyUnion)
|
||||||
|
|
||||||
def resolve_unions(self, args, context, info):
|
def resolve_unions(self, info):
|
||||||
return [one_object(), two_object()]
|
return [one_object(), two_object()]
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
hello_schema = Schema(Query)
|
||||||
|
@ -81,29 +100,31 @@ def test_query_interface():
|
||||||
base = String()
|
base = String()
|
||||||
|
|
||||||
class One(ObjectType):
|
class One(ObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
interfaces = (MyInterface, )
|
interfaces = (MyInterface, )
|
||||||
|
|
||||||
one = String()
|
one = String()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_type_of(cls, root, context, info):
|
def is_type_of(cls, root, info):
|
||||||
return isinstance(root, one_object)
|
return isinstance(root, one_object)
|
||||||
|
|
||||||
class Two(ObjectType):
|
class Two(ObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
interfaces = (MyInterface, )
|
interfaces = (MyInterface, )
|
||||||
|
|
||||||
two = String()
|
two = String()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_type_of(cls, root, context, info):
|
def is_type_of(cls, root, info):
|
||||||
return isinstance(root, two_object)
|
return isinstance(root, two_object)
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
interfaces = List(MyInterface)
|
interfaces = List(MyInterface)
|
||||||
|
|
||||||
def resolve_interfaces(self, args, context, info):
|
def resolve_interfaces(self, info):
|
||||||
return [one_object(), two_object()]
|
return [one_object(), two_object()]
|
||||||
|
|
||||||
hello_schema = Schema(Query, types=[One, Two])
|
hello_schema = Schema(Query, types=[One, Two])
|
||||||
|
@ -123,13 +144,15 @@ def test_query_dynamic():
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
hello = Dynamic(lambda: String(resolver=lambda *_: 'World'))
|
hello = Dynamic(lambda: String(resolver=lambda *_: 'World'))
|
||||||
hellos = Dynamic(lambda: List(String, resolver=lambda *_: ['Worlds']))
|
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)
|
hello_schema = Schema(Query)
|
||||||
|
|
||||||
executed = hello_schema.execute('{ hello hellos helloField }')
|
executed = hello_schema.execute('{ hello hellos helloField }')
|
||||||
assert not executed.errors
|
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():
|
def test_query_default_value():
|
||||||
|
@ -151,7 +174,7 @@ def test_query_wrong_default_value():
|
||||||
field = String()
|
field = String()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_type_of(cls, root, context, info):
|
def is_type_of(cls, root, info):
|
||||||
return isinstance(root, MyType)
|
return isinstance(root, MyType)
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
|
@ -161,7 +184,8 @@ def test_query_wrong_default_value():
|
||||||
|
|
||||||
executed = hello_schema.execute('{ hello { field } }')
|
executed = hello_schema.execute('{ hello { field } }')
|
||||||
assert len(executed.errors) == 1
|
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}
|
assert executed.data == {'hello': None}
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,7 +194,8 @@ def test_query_default_value_ignored_by_resolver():
|
||||||
field = String()
|
field = String()
|
||||||
|
|
||||||
class Query(ObjectType):
|
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)
|
hello_schema = Schema(Query)
|
||||||
|
|
||||||
|
@ -183,7 +208,7 @@ def test_query_resolve_function():
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
hello = String()
|
hello = String()
|
||||||
|
|
||||||
def resolve_hello(self, args, context, info):
|
def resolve_hello(self, info):
|
||||||
return 'World'
|
return 'World'
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
hello_schema = Schema(Query)
|
||||||
|
@ -197,7 +222,7 @@ def test_query_arguments():
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
test = String(a_str=String(), a_int=Int())
|
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=(',', ':'))
|
return json.dumps([self, args], separators=(',', ':'))
|
||||||
|
|
||||||
test_schema = Schema(Query)
|
test_schema = Schema(Query)
|
||||||
|
@ -210,7 +235,8 @@ def test_query_arguments():
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {'test': '["Source!",{"a_str":"String!"}]'}
|
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 not result.errors
|
||||||
assert result.data in [
|
assert result.data in [
|
||||||
{'test': '["Source!",{"a_str":"String!","a_int":-123}]'},
|
{'test': '["Source!",{"a_str":"String!","a_int":-123}]'},
|
||||||
|
@ -226,7 +252,7 @@ def test_query_input_field():
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
test = String(a_input=Input())
|
test = String(a_input=Input())
|
||||||
|
|
||||||
def resolve_test(self, args, context, info):
|
def resolve_test(self, info, **args):
|
||||||
return json.dumps([self, args], separators=(',', ':'))
|
return json.dumps([self, args], separators=(',', ':'))
|
||||||
|
|
||||||
test_schema = Schema(Query)
|
test_schema = Schema(Query)
|
||||||
|
@ -235,13 +261,17 @@ def test_query_input_field():
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {'test': '[null,{}]'}
|
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 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 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():
|
def test_query_middlewares():
|
||||||
|
@ -249,10 +279,10 @@ def test_query_middlewares():
|
||||||
hello = String()
|
hello = String()
|
||||||
other = String()
|
other = String()
|
||||||
|
|
||||||
def resolve_hello(self, args, context, info):
|
def resolve_hello(self, info):
|
||||||
return 'World'
|
return 'World'
|
||||||
|
|
||||||
def resolve_other(self, args, context, info):
|
def resolve_other(self, info):
|
||||||
return 'other'
|
return 'other'
|
||||||
|
|
||||||
def reversed_middleware(next, *args, **kwargs):
|
def reversed_middleware(next, *args, **kwargs):
|
||||||
|
@ -261,27 +291,29 @@ def test_query_middlewares():
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
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 not executed.errors
|
||||||
assert executed.data == {'hello': 'dlroW', 'other': 'rehto'}
|
assert executed.data == {'hello': 'dlroW', 'other': 'rehto'}
|
||||||
|
|
||||||
|
|
||||||
def test_objecttype_on_instances():
|
def test_objecttype_on_instances():
|
||||||
class Ship:
|
class Ship:
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
class ShipType(ObjectType):
|
class ShipType(ObjectType):
|
||||||
name = String(description="Ship name", required=True)
|
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
|
# Here self will be the Ship instance returned in resolve_ship
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
ship = Field(ShipType)
|
ship = Field(ShipType)
|
||||||
|
|
||||||
def resolve_ship(self, context, args, info):
|
def resolve_ship(self, info):
|
||||||
return Ship(name='xwing')
|
return Ship(name='xwing')
|
||||||
|
|
||||||
schema = Schema(query=Query)
|
schema = Schema(query=Query)
|
||||||
|
@ -296,7 +328,7 @@ def test_big_list_query_benchmark(benchmark):
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
all_ints = List(Int)
|
all_ints = List(Int)
|
||||||
|
|
||||||
def resolve_all_ints(self, args, context, info):
|
def resolve_all_ints(self, info):
|
||||||
return big_list
|
return big_list
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
hello_schema = Schema(Query)
|
||||||
|
@ -313,7 +345,7 @@ def test_big_list_query_compiled_query_benchmark(benchmark):
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
all_ints = List(Int)
|
all_ints = List(Int)
|
||||||
|
|
||||||
def resolve_all_ints(self, args, context, info):
|
def resolve_all_ints(self, info):
|
||||||
return big_list
|
return big_list
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
hello_schema = Schema(Query)
|
||||||
|
@ -335,7 +367,7 @@ def test_big_list_of_containers_query_benchmark(benchmark):
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
all_containers = List(Container)
|
all_containers = List(Container)
|
||||||
|
|
||||||
def resolve_all_containers(self, args, context, info):
|
def resolve_all_containers(self, info):
|
||||||
return big_container_list
|
return big_container_list
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
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 } }')
|
big_list_query = partial(hello_schema.execute, '{ allContainers { x } }')
|
||||||
result = benchmark(big_list_query)
|
result = benchmark(big_list_query)
|
||||||
assert not result.errors
|
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):
|
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):
|
class Query(ObjectType):
|
||||||
all_containers = List(Container)
|
all_containers = List(Container)
|
||||||
|
|
||||||
def resolve_all_containers(self, args, context, info):
|
def resolve_all_containers(self, info):
|
||||||
return big_container_list
|
return big_container_list
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
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)
|
result = benchmark(big_list_query)
|
||||||
assert not result.errors
|
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):
|
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()
|
z = Int()
|
||||||
o = Int()
|
o = Int()
|
||||||
|
|
||||||
def resolve_x(self, args, context, info):
|
def resolve_x(self, info):
|
||||||
return self.x
|
return self.x
|
||||||
|
|
||||||
def resolve_y(self, args, context, info):
|
def resolve_y(self, info):
|
||||||
return self.y
|
return self.y
|
||||||
|
|
||||||
def resolve_z(self, args, context, info):
|
def resolve_z(self, info):
|
||||||
return self.z
|
return self.z
|
||||||
|
|
||||||
def resolve_o(self, args, context, info):
|
def resolve_o(self, info):
|
||||||
return self.o
|
return self.o
|
||||||
|
|
||||||
big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(1000)]
|
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):
|
class Query(ObjectType):
|
||||||
all_containers = List(Container)
|
all_containers = List(Container)
|
||||||
|
|
||||||
def resolve_all_containers(self, args, context, info):
|
def resolve_all_containers(self, info):
|
||||||
return big_container_list
|
return big_container_list
|
||||||
|
|
||||||
hello_schema = Schema(Query)
|
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)
|
result = benchmark(big_list_query)
|
||||||
assert not result.errors
|
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'}
|
||||||
|
|
|
@ -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 = {}
|
args = {}
|
||||||
context = None
|
context = None
|
||||||
|
@ -16,22 +16,22 @@ class demo_obj(object):
|
||||||
|
|
||||||
|
|
||||||
def test_attr_resolver():
|
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'
|
assert resolved == 'value'
|
||||||
|
|
||||||
|
|
||||||
def test_attr_resolver_default_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'
|
assert resolved == 'default'
|
||||||
|
|
||||||
|
|
||||||
def test_dict_resolver():
|
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'
|
assert resolved == 'value'
|
||||||
|
|
||||||
|
|
||||||
def test_dict_resolver_default_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'
|
assert resolved == 'default'
|
||||||
|
|
||||||
|
|
||||||
|
|
10
graphene/types/tests/test_scalar.py
Normal file
10
graphene/types/tests/test_scalar.py
Normal 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"
|
|
@ -1,9 +1,9 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..schema import Schema
|
from ..field import Field
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
from ..field import Field
|
from ..schema import Schema
|
||||||
|
|
||||||
|
|
||||||
class MyOtherType(ObjectType):
|
class MyOtherType(ObjectType):
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import pytest
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from ..structures import List, NonNull
|
import pytest
|
||||||
|
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
|
from ..structures import List, NonNull
|
||||||
from .utils import MyLazyType
|
from .utils import MyLazyType
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,8 @@ def test_objecttype():
|
||||||
foo = String(bar=String(description='Argument description', default_value='x'), description='Field description')
|
foo = String(bar=String(description='Argument description', default_value='x'), description='Field description')
|
||||||
bar = String(name='gizmo')
|
bar = String(name='gizmo')
|
||||||
|
|
||||||
def resolve_foo(self, args, info):
|
def resolve_foo(self, bar):
|
||||||
return args.get('bar')
|
return bar
|
||||||
|
|
||||||
typemap = TypeMap([MyObjectType])
|
typemap = TypeMap([MyObjectType])
|
||||||
assert 'MyObjectType' in typemap
|
assert 'MyObjectType' in typemap
|
||||||
|
@ -65,7 +65,7 @@ def test_objecttype():
|
||||||
assert isinstance(foo_field, GraphQLField)
|
assert isinstance(foo_field, GraphQLField)
|
||||||
assert foo_field.description == 'Field description'
|
assert foo_field.description == 'Field description'
|
||||||
f = MyObjectType.resolve_foo
|
f = MyObjectType.resolve_foo
|
||||||
assert foo_field.resolver == getattr(f, '__func__', f)
|
# assert foo_field.resolver == getattr(f, '__func__', f)
|
||||||
assert foo_field.args == {
|
assert foo_field.args == {
|
||||||
'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar')
|
'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.name == 'MyInputObjectType'
|
||||||
assert graphql_type.description == 'Description'
|
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
|
fields = graphql_type.fields
|
||||||
assert list(fields.keys()) == ['fooBar', 'gizmo', 'own']
|
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']
|
foo_field = fields['fooBar']
|
||||||
assert isinstance(foo_field, GraphQLInputObjectField)
|
assert isinstance(foo_field, GraphQLInputObjectField)
|
||||||
assert foo_field.description == 'Field description'
|
assert foo_field.description == 'Field description'
|
||||||
|
@ -196,5 +204,5 @@ def test_objecttype_with_possible_types():
|
||||||
typemap = TypeMap([MyObjectType])
|
typemap = TypeMap([MyObjectType])
|
||||||
graphql_type = typemap['MyObjectType']
|
graphql_type = typemap['MyObjectType']
|
||||||
assert graphql_type.is_type_of
|
assert graphql_type.is_type_of
|
||||||
assert graphql_type.is_type_of({}, None, None) is True
|
assert graphql_type.is_type_of({}, None) is True
|
||||||
assert graphql_type.is_type_of(MyObjectType(), None, None) is False
|
assert graphql_type.is_type_of(MyObjectType(), None) is False
|
||||||
|
|
|
@ -47,6 +47,7 @@ def test_generate_union_with_no_types():
|
||||||
|
|
||||||
def test_union_can_be_mounted():
|
def test_union_can_be_mounted():
|
||||||
class MyUnion(Union):
|
class MyUnion(Union):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
types = (MyObjectType1, MyObjectType2)
|
types = (MyObjectType1, MyObjectType2)
|
||||||
|
|
||||||
|
|
34
graphene/types/tests/test_uuid.py
Normal file
34
graphene/types/tests/test_uuid.py
Normal 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
|
||||||
|
}
|
|
@ -11,10 +11,10 @@ from graphql.type.typemap import GraphQLTypeMap
|
||||||
|
|
||||||
from ..utils.get_unbound_function import get_unbound_function
|
from ..utils.get_unbound_function import get_unbound_function
|
||||||
from ..utils.str_converters import to_camel_case
|
from ..utils.str_converters import to_camel_case
|
||||||
from .definitions import (GrapheneEnumType, GrapheneInputObjectType,
|
from .definitions import (GrapheneEnumType, GrapheneGraphQLType,
|
||||||
GrapheneInterfaceType, GrapheneObjectType,
|
GrapheneInputObjectType, GrapheneInterfaceType,
|
||||||
GrapheneScalarType, GrapheneUnionType,
|
GrapheneObjectType, GrapheneScalarType,
|
||||||
GrapheneGraphQLType)
|
GrapheneUnionType)
|
||||||
from .dynamic import Dynamic
|
from .dynamic import Dynamic
|
||||||
from .enum import Enum
|
from .enum import Enum
|
||||||
from .field import Field
|
from .field import Field
|
||||||
|
@ -37,12 +37,12 @@ def is_graphene_type(_type):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def resolve_type(resolve_type_func, map, type_name, root, context, info):
|
def resolve_type(resolve_type_func, map, type_name, root, info):
|
||||||
_type = resolve_type_func(root, context, info)
|
_type = resolve_type_func(root, info)
|
||||||
|
|
||||||
if not _type:
|
if not _type:
|
||||||
return_type = map[type_name]
|
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):
|
if inspect.isclass(_type) and issubclass(_type, ObjectType):
|
||||||
graphql_type = map.get(_type._meta.name)
|
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
|
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)
|
return isinstance(root, possible_types)
|
||||||
|
|
||||||
|
|
||||||
class TypeMap(GraphQLTypeMap):
|
class TypeMap(GraphQLTypeMap):
|
||||||
|
|
||||||
def __init__(self, types, auto_camelcase=True, schema=None):
|
def __init__(self, types, auto_camelcase=True, schema=None):
|
||||||
self.auto_camelcase = auto_camelcase
|
self.auto_camelcase = auto_camelcase
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
@ -194,6 +195,7 @@ class TypeMap(GraphQLTypeMap):
|
||||||
graphene_type=type,
|
graphene_type=type,
|
||||||
name=type._meta.name,
|
name=type._meta.name,
|
||||||
description=type._meta.description,
|
description=type._meta.description,
|
||||||
|
container_type=type._meta.container,
|
||||||
fields=partial(
|
fields=partial(
|
||||||
self.construct_fields_for_type, map, type, is_input_type=True),
|
self.construct_fields_for_type, map, type, is_input_type=True),
|
||||||
)
|
)
|
||||||
|
@ -237,7 +239,7 @@ class TypeMap(GraphQLTypeMap):
|
||||||
_field = GraphQLInputObjectField(
|
_field = GraphQLInputObjectField(
|
||||||
field_type,
|
field_type,
|
||||||
default_value=field.default_value,
|
default_value=field.default_value,
|
||||||
out_name=field.name or name,
|
out_name=name,
|
||||||
description=field.description)
|
description=field.description)
|
||||||
else:
|
else:
|
||||||
args = OrderedDict()
|
args = OrderedDict()
|
||||||
|
@ -254,8 +256,12 @@ class TypeMap(GraphQLTypeMap):
|
||||||
field_type,
|
field_type,
|
||||||
args=args,
|
args=args,
|
||||||
resolver=field.get_resolver(
|
resolver=field.get_resolver(
|
||||||
self.get_resolver_for_type(type, name,
|
self.get_resolver_for_type(
|
||||||
field.default_value)),
|
type,
|
||||||
|
name,
|
||||||
|
field.default_value
|
||||||
|
)
|
||||||
|
),
|
||||||
deprecation_reason=field.deprecation_reason,
|
deprecation_reason=field.deprecation_reason,
|
||||||
description=field.description)
|
description=field.description)
|
||||||
field_name = field.name or self.get_name(name)
|
field_name = field.name or self.get_name(name)
|
||||||
|
|
|
@ -1,38 +1,19 @@
|
||||||
import six
|
from .base import BaseOptions, BaseType
|
||||||
|
|
||||||
from ..utils.is_base_type import is_base_type
|
|
||||||
from ..utils.trim_docstring import trim_docstring
|
|
||||||
from .options import Options
|
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
|
|
||||||
|
|
||||||
class UnionMeta(type):
|
# For static type checking with Mypy
|
||||||
|
MYPY = False
|
||||||
def __new__(cls, name, bases, attrs):
|
if MYPY:
|
||||||
# Also ensure initialization is only performed for subclasses of
|
from .objecttype import ObjectType # NOQA
|
||||||
# Union
|
from typing import Iterable, Type # NOQA
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class Union(six.with_metaclass(UnionMeta, UnmountedType)):
|
class UnionOptions(BaseOptions):
|
||||||
|
types = () # type: Iterable[Type[ObjectType]]
|
||||||
|
|
||||||
|
|
||||||
|
class Union(UnmountedType, BaseType):
|
||||||
'''
|
'''
|
||||||
Union Type Definition
|
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
|
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.
|
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
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
|
@ -50,7 +41,7 @@ class Union(six.with_metaclass(UnionMeta, UnmountedType)):
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resolve_type(cls, instance, context, info):
|
def resolve_type(cls, instance, info):
|
||||||
from .objecttype import ObjectType
|
from .objecttype import ObjectType # NOQA
|
||||||
if isinstance(instance, ObjectType):
|
if isinstance(instance, ObjectType):
|
||||||
return type(instance)
|
return type(instance)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import inspect
|
import inspect
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
from ..utils.module_loading import import_string
|
from ..utils.module_loading import import_string
|
||||||
|
@ -8,35 +9,6 @@ from .mountedtype import MountedType
|
||||||
from .unmountedtype import UnmountedType
|
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):
|
def get_field_as(value, _as=None):
|
||||||
'''
|
'''
|
||||||
Get type mounted
|
Get type mounted
|
||||||
|
@ -49,7 +21,7 @@ def get_field_as(value, _as=None):
|
||||||
return _as.mounted(value)
|
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)
|
Extract all the fields in given attributes (dict)
|
||||||
and return them ordered
|
and return them ordered
|
||||||
|
@ -60,8 +32,6 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True):
|
||||||
if not field:
|
if not field:
|
||||||
continue
|
continue
|
||||||
fields_with_names.append((attname, field))
|
fields_with_names.append((attname, field))
|
||||||
if delete:
|
|
||||||
del attrs[attname]
|
|
||||||
|
|
||||||
if sort:
|
if sort:
|
||||||
fields_with_names = sorted(fields_with_names, key=lambda f: f[1])
|
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):
|
def get_type(_type):
|
||||||
if isinstance(_type, string_types):
|
if isinstance(_type, string_types):
|
||||||
return import_string(_type)
|
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()
|
||||||
return _type
|
return _type
|
||||||
|
|
27
graphene/types/uuid.py
Normal file
27
graphene/types/uuid.py
Normal 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)
|
36
graphene/utils/annotate.py
Normal file
36
graphene/utils/annotate.py
Normal 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
|
80
graphene/utils/deprecated.py
Normal file
80
graphene/utils/deprecated.py
Normal 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)))
|
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
def is_base_type(bases, _type):
|
|
||||||
return any(b for b in bases if isinstance(b, _type))
|
|
|
@ -1,8 +1,11 @@
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from .deprecated import deprecated
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated('This function is deprecated')
|
||||||
def resolve_only_args(func):
|
def resolve_only_args(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def inner(root, args, context, info):
|
def wrapped_func(root, info, **args):
|
||||||
return func(root, **args)
|
return func(root, **args)
|
||||||
return inner
|
|
||||||
|
return wrapped_func
|
||||||
|
|
44
graphene/utils/subclass_with_meta.py
Normal file
44
graphene/utils/subclass_with_meta.py
Normal 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"""
|
33
graphene/utils/tests/test_annotate.py
Normal file
33
graphene/utils/tests/test_annotate.py
Normal 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".'
|
65
graphene/utils/tests/test_deprecated.py
Normal file
65
graphene/utils/tests/test_deprecated.py
Normal 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({})
|
|
@ -1,16 +1,16 @@
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
|
|
||||||
from graphene import String
|
from graphene import ObjectType, String
|
||||||
from graphene.types.objecttype import ObjectTypeMeta
|
|
||||||
from ..module_loading import lazy_import, import_string
|
from ..module_loading import import_string, lazy_import
|
||||||
|
|
||||||
|
|
||||||
def test_import_string():
|
def test_import_string():
|
||||||
MyString = import_string('graphene.String')
|
MyString = import_string('graphene.String')
|
||||||
assert MyString == String
|
assert MyString == String
|
||||||
|
|
||||||
MyObjectTypeMeta = import_string('graphene.ObjectType', '__class__')
|
MyObjectTypeMeta = import_string('graphene.ObjectType', '__doc__')
|
||||||
assert MyObjectTypeMeta == ObjectTypeMeta
|
assert MyObjectTypeMeta == ObjectType.__doc__
|
||||||
|
|
||||||
|
|
||||||
def test_import_string_module():
|
def test_import_string_module():
|
||||||
|
@ -52,6 +52,6 @@ def test_lazy_import():
|
||||||
MyString = f()
|
MyString = f()
|
||||||
assert MyString == String
|
assert MyString == String
|
||||||
|
|
||||||
f = lazy_import('graphene.ObjectType', '__class__')
|
f = lazy_import('graphene.ObjectType', '__doc__')
|
||||||
MyObjectTypeMeta = f()
|
MyObjectTypeMeta = f()
|
||||||
assert MyObjectTypeMeta == ObjectTypeMeta
|
assert MyObjectTypeMeta == ObjectType.__doc__
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from ..resolve_only_args import resolve_only_args
|
from ..resolve_only_args import resolve_only_args
|
||||||
|
from .. import deprecated
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_only_args():
|
def test_resolve_only_args(mocker):
|
||||||
|
mocker.patch.object(deprecated, 'warn_deprecation')
|
||||||
def resolver(*args, **kwargs):
|
def resolver(root, **args):
|
||||||
return kwargs
|
return root, args
|
||||||
|
|
||||||
my_data = {'one': 1, 'two': 2}
|
my_data = {'one': 1, 'two': 2}
|
||||||
|
|
||||||
wrapped = resolve_only_args(resolver)
|
wrapped_resolver = resolve_only_args(resolver)
|
||||||
assert wrapped(None, my_data, None, None) == my_data
|
assert deprecated.warn_deprecation.called
|
||||||
|
result = wrapped_resolver(1, 2, a=3)
|
||||||
|
assert result == (1, {'a': 3})
|
||||||
|
|
|
@ -9,7 +9,6 @@ def test_trim_docstring():
|
||||||
|
|
||||||
Multiple paragraphs too
|
Multiple paragraphs too
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
assert (trim_docstring(WellDocumentedObject.__doc__) ==
|
assert (trim_docstring(WellDocumentedObject.__doc__) ==
|
||||||
"This object is very well-documented. It has multiple lines in its\n"
|
"This object is very well-documented. It has multiple lines in its\n"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user