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