mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-31 07:57:26 +03:00 
			
		
		
		
	
						commit
						0b92d3dba6
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -80,3 +80,4 @@ target/ | ||||||
| # Databases | # Databases | ||||||
| *.sqlite3 | *.sqlite3 | ||||||
| .vscode | .vscode | ||||||
|  | .mypy_cache | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							|  | @ -2,8 +2,8 @@ language: python | ||||||
| sudo: false | sudo: false | ||||||
| python: | python: | ||||||
| - 2.7 | - 2.7 | ||||||
| - 3.4 |  | ||||||
| - 3.5 | - 3.5 | ||||||
|  | - 3.6 | ||||||
| - pypy | - pypy | ||||||
| before_install: | before_install: | ||||||
| - | | - | | ||||||
|  | @ -26,6 +26,8 @@ install: | ||||||
|     python setup.py develop |     python setup.py develop | ||||||
|   elif [ "$TEST_TYPE" = lint ]; then |   elif [ "$TEST_TYPE" = lint ]; then | ||||||
|     pip install flake8 |     pip install flake8 | ||||||
|  |   elif [ "$TEST_TYPE" = mypy ]; then | ||||||
|  |     pip install mypy | ||||||
|   fi |   fi | ||||||
| script: | script: | ||||||
| - | | - | | ||||||
|  | @ -33,6 +35,10 @@ script: | ||||||
|     echo "Checking Python code lint." |     echo "Checking Python code lint." | ||||||
|     flake8 graphene |     flake8 graphene | ||||||
|     exit |     exit | ||||||
|  |   elif [ "$TEST_TYPE" = mypy ]; then | ||||||
|  |     echo "Checking Python types." | ||||||
|  |     mypy graphene | ||||||
|  |     exit | ||||||
|   elif [ "$TEST_TYPE" = build ]; then |   elif [ "$TEST_TYPE" = build ]; then | ||||||
|     py.test --cov=graphene graphene examples |     py.test --cov=graphene graphene examples | ||||||
|   fi |   fi | ||||||
|  | @ -51,6 +57,8 @@ matrix: | ||||||
|   include: |   include: | ||||||
|   - python: '2.7' |   - python: '2.7' | ||||||
|     env: TEST_TYPE=lint |     env: TEST_TYPE=lint | ||||||
|  |   - python: '3.6' | ||||||
|  |     env: TEST_TYPE=mypy | ||||||
| deploy: | deploy: | ||||||
|   provider: pypi |   provider: pypi | ||||||
|   user: syrusakbary |   user: syrusakbary | ||||||
|  | @ -58,3 +66,4 @@ deploy: | ||||||
|     tags: true |     tags: true | ||||||
|   password: |   password: | ||||||
|     secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ= |     secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ= | ||||||
|  |   distributions: "sdist bdist_wheel" | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							|  | @ -1,4 +1,4 @@ | ||||||
| Please read [UPGRADE-v1.0.md](/UPGRADE-v1.0.md) to learn how to upgrade to Graphene `1.0`. | Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade to Graphene `2.0`. | ||||||
| 
 | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
|  | @ -32,12 +32,12 @@ Also, Graphene is fully compatible with the GraphQL spec, working seamlessly wit | ||||||
| For instaling graphene, just run this command in your shell | For instaling graphene, just run this command in your shell | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| pip install "graphene>=1.0" | pip install "graphene>=2.0.dev" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## 1.0 Upgrade Guide | ## 2.0 Upgrade Guide | ||||||
| 
 | 
 | ||||||
| Please read [UPGRADE-v1.0.md](/UPGRADE-v1.0.md) to learn how to upgrade. | Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## Examples | ## Examples | ||||||
|  | @ -48,7 +48,7 @@ Here is one example for you to get started: | ||||||
| class Query(graphene.ObjectType): | class Query(graphene.ObjectType): | ||||||
|     hello = graphene.String(description='A typical hello world') |     hello = graphene.String(description='A typical hello world') | ||||||
| 
 | 
 | ||||||
|     def resolve_hello(self, args, context, info): |     def resolve_hello(self, info): | ||||||
|         return 'World' |         return 'World' | ||||||
| 
 | 
 | ||||||
| schema = graphene.Schema(query=Query) | schema = graphene.Schema(query=Query) | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.rst
									
									
									
									
									
								
							|  | @ -1,5 +1,5 @@ | ||||||
| Please read `UPGRADE-v1.0.md </UPGRADE-v1.0.md>`__ to learn how to | Please read `UPGRADE-v2.0.md </UPGRADE-v2.0.md>`__ to learn how to | ||||||
| upgrade to Graphene ``1.0``. | upgrade to Graphene ``2.0``. | ||||||
| 
 | 
 | ||||||
| -------------- | -------------- | ||||||
| 
 | 
 | ||||||
|  | @ -11,7 +11,7 @@ building GraphQL schemas/types fast and easily. | ||||||
| 
 | 
 | ||||||
| -  **Easy to use:** Graphene helps you use GraphQL in Python without | -  **Easy to use:** Graphene helps you use GraphQL in Python without | ||||||
|    effort. |    effort. | ||||||
| -  **Relay:** Graphene has builtin support for both Relay. | -  **Relay:** Graphene has builtin support for Relay. | ||||||
| -  **Data agnostic:** Graphene supports any kind of data source: SQL | -  **Data agnostic:** Graphene supports any kind of data source: SQL | ||||||
|    (Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe |    (Django, SQLAlchemy), NoSQL, custom Python objects, etc. We believe | ||||||
|    that by providing a complete API you could plug Graphene anywhere |    that by providing a complete API you could plug Graphene anywhere | ||||||
|  | @ -47,12 +47,12 @@ For instaling graphene, just run this command in your shell | ||||||
| 
 | 
 | ||||||
| .. code:: bash | .. code:: bash | ||||||
| 
 | 
 | ||||||
|     pip install "graphene>=1.0" |     pip install "graphene>=2.0" | ||||||
| 
 | 
 | ||||||
| 1.0 Upgrade Guide | 2.0 Upgrade Guide | ||||||
| ----------------- | ----------------- | ||||||
| 
 | 
 | ||||||
| Please read `UPGRADE-v1.0.md </UPGRADE-v1.0.md>`__ to learn how to | Please read `UPGRADE-v2.0.md </UPGRADE-v2.0.md>`__ to learn how to | ||||||
| upgrade. | upgrade. | ||||||
| 
 | 
 | ||||||
| Examples | Examples | ||||||
|  | @ -65,7 +65,7 @@ Here is one example for you to get started: | ||||||
|     class Query(graphene.ObjectType): |     class Query(graphene.ObjectType): | ||||||
|         hello = graphene.String(description='A typical hello world') |         hello = graphene.String(description='A typical hello world') | ||||||
| 
 | 
 | ||||||
|         def resolve_hello(self, args, context, info): |         def resolve_hello(self, info): | ||||||
|             return 'World' |             return 'World' | ||||||
| 
 | 
 | ||||||
|     schema = graphene.Schema(query=Query) |     schema = graphene.Schema(query=Query) | ||||||
|  |  | ||||||
							
								
								
									
										293
									
								
								UPGRADE-v2.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								UPGRADE-v2.0.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,293 @@ | ||||||
|  | # v2.0 Upgrade Guide | ||||||
|  | 
 | ||||||
|  | `ObjectType`, `Interface`, `InputObjectType`, `Scalar` and `Enum` implementations | ||||||
|  | have been quite simplified, without the need to define a explicit Metaclass for each subtype. | ||||||
|  | 
 | ||||||
|  | It also improves the field resolvers, [simplifying the code](#simpler-resolvers) the | ||||||
|  | developer has to write to use them. | ||||||
|  | 
 | ||||||
|  | **Deprecations:** | ||||||
|  | * [`AbstractType`](#abstracttype-deprecated) | ||||||
|  | * [`resolve_only_args`](#resolve_only_args) | ||||||
|  | * [`Mutation.Input`](#mutationinput) | ||||||
|  | 
 | ||||||
|  | **Breaking changes:** | ||||||
|  | * [`Simpler Resolvers`](#simpler-resolvers) | ||||||
|  | * [`Node Connections`](#node-connections) | ||||||
|  | 
 | ||||||
|  | **New Features!** | ||||||
|  | * [`InputObjectType`](#inputobjecttype) | ||||||
|  | * [`Meta as Class arguments`](#meta-ass-class-arguments) (_only available for Python 3_) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | > The type metaclasses are now deleted as they are no longer necessary.  If your code was depending | ||||||
|  | > on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/2.0/graphene/tests/issues/test_425.py). | ||||||
|  | 
 | ||||||
|  | ## Deprecations | ||||||
|  | 
 | ||||||
|  | ### AbstractType deprecated | ||||||
|  | 
 | ||||||
|  | AbstractType is deprecated in graphene 2.0, you can now use normal inheritance instead. | ||||||
|  | 
 | ||||||
|  | Before: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class CommonFields(AbstractType): | ||||||
|  |     name = String() | ||||||
|  | 
 | ||||||
|  | class Pet(CommonFields, Interface): | ||||||
|  |     pass | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | With 2.0: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class CommonFields(object): | ||||||
|  |     name = String() | ||||||
|  | 
 | ||||||
|  | class Pet(CommonFields, Interface): | ||||||
|  |     pass | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### resolve\_only\_args | ||||||
|  | 
 | ||||||
|  | `resolve_only_args` is now deprecated as the resolver API has been simplified. | ||||||
|  | 
 | ||||||
|  | Before: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class User(ObjectType): | ||||||
|  |     name = String() | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_name(self): | ||||||
|  |         return self.name | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | With 2.0: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class User(ObjectType): | ||||||
|  |     name = String() | ||||||
|  | 
 | ||||||
|  |     def resolve_name(self, info): | ||||||
|  |         return self.name | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Mutation.Input | ||||||
|  | 
 | ||||||
|  | `Mutation.Input` is now deprecated in favor of using `Mutation.Arguments` (`ClientIDMutation` still uses `Input`). | ||||||
|  | 
 | ||||||
|  | Before: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class User(Mutation): | ||||||
|  |     class Input: | ||||||
|  |         name = String() | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | With 2.0: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class User(Mutation): | ||||||
|  |     class Arguments: | ||||||
|  |         name = String() | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## Breaking Changes | ||||||
|  | 
 | ||||||
|  | ### Simpler resolvers | ||||||
|  | 
 | ||||||
|  | All the resolvers in graphene have been simplified. | ||||||
|  | Prior to Graphene `2.0`, all resolvers required four arguments: `(root, args, context, info)`. | ||||||
|  | Now, resolver `args` are passed as keyword arguments to the function, and `context` argument dissapeared in favor of `info.context`. | ||||||
|  | 
 | ||||||
|  | Before: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | my_field = graphene.String(my_arg=graphene.String()) | ||||||
|  | 
 | ||||||
|  | def resolve_my_field(self, args, context, info): | ||||||
|  |     my_arg = args.get('my_arg') | ||||||
|  |     return ... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | With 2.0: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | my_field = graphene.String(my_arg=graphene.String()) | ||||||
|  | 
 | ||||||
|  | def resolve_my_field(self, info, my_arg): | ||||||
|  |     return ... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And, if you need the context in the resolver, you can use `info.context`: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | my_field = graphene.String(my_arg=graphene.String()) | ||||||
|  | 
 | ||||||
|  | def resolve_my_field(self, info, my_arg): | ||||||
|  |     context = info.context | ||||||
|  |     return ... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Node Connections | ||||||
|  | 
 | ||||||
|  | Node types no longer have a `Connection` by default. | ||||||
|  | In 2.0 and onwards `Connection`s should be defined explicitly. | ||||||
|  | 
 | ||||||
|  | Before: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class User(ObjectType): | ||||||
|  |     class Meta: | ||||||
|  |         interfaces = [relay.Node] | ||||||
|  |     name = String() | ||||||
|  | 
 | ||||||
|  | class Query(ObjectType): | ||||||
|  |     user_connection = relay.ConnectionField(User) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | With 2.0: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class User(ObjectType): | ||||||
|  |     class Meta: | ||||||
|  |         interfaces = [relay.Node] | ||||||
|  |     name = String() | ||||||
|  | 
 | ||||||
|  | class UserConnection(relay.Connection): | ||||||
|  |     class Meta: | ||||||
|  |         node = User | ||||||
|  | 
 | ||||||
|  | class Query(ObjectType): | ||||||
|  |     user_connection = relay.ConnectionField(UserConnection) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Node.get_node | ||||||
|  | 
 | ||||||
|  | The method `get_node` in `ObjectTypes` that have `Node` as interface, changes its API. | ||||||
|  | From `def get_node(cls, id, context, info)` to `def get_node(cls, info, id)`. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class MyObject(ObjectType): | ||||||
|  |     class Meta: | ||||||
|  |         interfaces = (Node, ) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def get_node(cls, id, context, info): | ||||||
|  |         return ... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | To: | ||||||
|  | ```python | ||||||
|  | class MyObject(ObjectType): | ||||||
|  |     class Meta: | ||||||
|  |         interfaces = (Node, ) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def get_node(cls, info, id): | ||||||
|  |         return ... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Mutation.mutate | ||||||
|  | 
 | ||||||
|  | Now only receives (`root`, `info`, `**args`) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## ClientIDMutation.mutate_and_get_payload | ||||||
|  | 
 | ||||||
|  | Now only receives (`root`, `info`, `**input`) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## New Features | ||||||
|  | 
 | ||||||
|  | ### InputObjectType | ||||||
|  | 
 | ||||||
|  | If you are using `InputObjectType`, you now can access | ||||||
|  | its fields via `getattr` (`my_input.myattr`) when resolving, instead of | ||||||
|  | the classic way `my_input['myattr']`. | ||||||
|  | 
 | ||||||
|  | And also use custom defined properties on your input class. | ||||||
|  | 
 | ||||||
|  | Example. Before: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class UserInput(InputObjectType): | ||||||
|  |     id = ID(required=True) | ||||||
|  | 
 | ||||||
|  | def is_valid_input(input): | ||||||
|  |     return input.get('id').startswith('userid_') | ||||||
|  | 
 | ||||||
|  | class Query(ObjectType): | ||||||
|  |     user = graphene.Field(User, input=UserInput()) | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_user(self, input): | ||||||
|  |         user_id = input.get('id') | ||||||
|  |         if is_valid_input(user_id): | ||||||
|  |             return get_user(user_id) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | With 2.0: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class UserInput(InputObjectType): | ||||||
|  |     id = ID(required=True) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_valid(self): | ||||||
|  |         return self.id.startswith('userid_') | ||||||
|  | 
 | ||||||
|  | class Query(ObjectType): | ||||||
|  |     user = graphene.Field(User, input=UserInput()) | ||||||
|  | 
 | ||||||
|  |     def resolve_user(self, info, input): | ||||||
|  |         if input.is_valid: | ||||||
|  |             return get_user(input.id) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Meta as Class arguments | ||||||
|  | 
 | ||||||
|  | Now you can use the meta options as class arguments (**ONLY PYTHON 3**). | ||||||
|  | 
 | ||||||
|  | Before: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class Dog(ObjectType): | ||||||
|  |     class Meta: | ||||||
|  |         interfaces = [Pet] | ||||||
|  |     name = String() | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | With 2.0: | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class Dog(ObjectType, interfaces=[Pet]): | ||||||
|  |     name = String() | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Abstract types | ||||||
|  | 
 | ||||||
|  | Now you can create abstact types super easily, without the need of subclassing the meta. | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | class Base(ObjectType): | ||||||
|  |     class Meta: | ||||||
|  |         abstract = True | ||||||
|  |      | ||||||
|  |     id = ID() | ||||||
|  | 
 | ||||||
|  |     def resolve_id(self, info): | ||||||
|  |         return "{type}_{id}".format( | ||||||
|  |             type=self.__class__.__name__, | ||||||
|  |             id=self.id | ||||||
|  |         ) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### UUID Scalar | ||||||
|  | 
 | ||||||
|  | In Graphene 2.0 there is a new dedicated scalar for UUIDs, `UUID`. | ||||||
|  | @ -99,8 +99,8 @@ leaner code and at most 4 database requests, and possibly fewer if there are cac | ||||||
|         best_friend = graphene.Field(lambda: User) |         best_friend = graphene.Field(lambda: User) | ||||||
|         friends = graphene.List(lambda: User) |         friends = graphene.List(lambda: User) | ||||||
| 
 | 
 | ||||||
|         def resolve_best_friend(self, args, context, info): |         def resolve_best_friend(self, info): | ||||||
|             return user_loader.load(self.best_friend_id) |             return user_loader.load(self.best_friend_id) | ||||||
| 
 | 
 | ||||||
|         def resolve_friends(self, args, context, info): |         def resolve_friends(self, info): | ||||||
|             return user_loader.load_many(self.friend_ids) |             return user_loader.load_many(self.friend_ids) | ||||||
|  |  | ||||||
|  | @ -24,8 +24,8 @@ You can pass context to a query via ``context_value``. | ||||||
|     class Query(graphene.ObjectType): |     class Query(graphene.ObjectType): | ||||||
|         name = graphene.String() |         name = graphene.String() | ||||||
| 
 | 
 | ||||||
|         def resolve_name(self, args, context, info): |         def resolve_name(self, info): | ||||||
|             return context.get('name') |             return info.context.get('name') | ||||||
| 
 | 
 | ||||||
|     schema = graphene.Schema(Query) |     schema = graphene.Schema(Query) | ||||||
|     result = schema.execute('{ name }', context_value={'name': 'Syrus'}) |     result = schema.execute('{ name }', context_value={'name': 'Syrus'}) | ||||||
|  | @ -43,8 +43,8 @@ You can pass variables to a query via ``variable_values``. | ||||||
|     class Query(graphene.ObjectType): |     class Query(graphene.ObjectType): | ||||||
|         user = graphene.Field(User) |         user = graphene.Field(User) | ||||||
| 
 | 
 | ||||||
|         def resolve_user(self, args, context, info): |         def resolve_user(self, info): | ||||||
|             return context.get('user') |             return info.context.get('user') | ||||||
| 
 | 
 | ||||||
|     schema = graphene.Schema(Query) |     schema = graphene.Schema(Query) | ||||||
|     result = schema.execute( |     result = schema.execute( | ||||||
|  |  | ||||||
|  | @ -31,10 +31,10 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'` | ||||||
| .. code:: python | .. code:: python | ||||||
| 
 | 
 | ||||||
|     class AuthorizationMiddleware(object): |     class AuthorizationMiddleware(object): | ||||||
|         def resolve(self, next, root, args, context, info): |         def resolve(self, next, root, info, **args): | ||||||
|             if info.field_name == 'user': |             if info.field_name == 'user': | ||||||
|                 return None |                 return None | ||||||
|             return next(root, args, context, info) |             return next(root, info, **args) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| And then execute it with: | And then execute it with: | ||||||
|  |  | ||||||
|  | @ -12,15 +12,15 @@ Let’s build a basic GraphQL schema from scratch. | ||||||
| Requirements | Requirements | ||||||
| ------------ | ------------ | ||||||
| 
 | 
 | ||||||
| -  Python (2.7, 3.2, 3.3, 3.4, 3.5, pypy) | -  Python (2.7, 3.4, 3.5, 3.6, pypy) | ||||||
| -  Graphene (1.0) | -  Graphene (2.0) | ||||||
| 
 | 
 | ||||||
| Project setup | Project setup | ||||||
| ------------- | ------------- | ||||||
| 
 | 
 | ||||||
| .. code:: bash | .. code:: bash | ||||||
| 
 | 
 | ||||||
|     pip install "graphene>=1.0" |     pip install "graphene>=2.0" | ||||||
| 
 | 
 | ||||||
| Creating a basic Schema | Creating a basic Schema | ||||||
| ----------------------- | ----------------------- | ||||||
|  | @ -37,10 +37,10 @@ one field: ``hello`` and an input name. And when we query it, it should return ` | ||||||
|     import graphene |     import graphene | ||||||
| 
 | 
 | ||||||
|     class Query(graphene.ObjectType): |     class Query(graphene.ObjectType): | ||||||
|         hello = graphene.String(name=graphene.Argument(graphene.String, default_value="stranger")) |         hello = graphene.String(name=graphene.String(default_value="stranger")) | ||||||
| 
 | 
 | ||||||
|         def resolve_hello(self, args, context, info): |         def resolve_hello(self, info, name): | ||||||
|             return 'Hello ' + args['name'] |             return 'Hello ' + name | ||||||
| 
 | 
 | ||||||
|     schema = graphene.Schema(query=Query) |     schema = graphene.Schema(query=Query) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -41,5 +41,5 @@ that implements ``Node`` will have a default Connection. | ||||||
|         name = graphene.String() |         name = graphene.String() | ||||||
|         ships = relay.ConnectionField(ShipConnection) |         ships = relay.ConnectionField(ShipConnection) | ||||||
| 
 | 
 | ||||||
|         def resolve_ships(self, args, context, info): |         def resolve_ships(self, info): | ||||||
|             return [] |             return [] | ||||||
|  |  | ||||||
|  | @ -21,9 +21,9 @@ subclass of ``relay.ClientIDMutation``. | ||||||
|         faction = graphene.Field(Faction) |         faction = graphene.Field(Faction) | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def mutate_and_get_payload(cls, input, context, info): |         def mutate_and_get_payload(cls, root, info, **input): | ||||||
|             ship_name = input.get('ship_name') |             ship_name = input.ship_name | ||||||
|             faction_id = input.get('faction_id') |             faction_id = input.faction_id | ||||||
|             ship = create_ship(ship_name, faction_id) |             ship = create_ship(ship_name, faction_id) | ||||||
|             faction = get_faction(faction_id) |             faction = get_faction(faction_id) | ||||||
|             return IntroduceShip(ship=ship, faction=faction) |             return IntroduceShip(ship=ship, faction=faction) | ||||||
|  | @ -46,7 +46,7 @@ Mutations can also accept files, that's how it will work with different integrat | ||||||
|          success = graphene.String() |          success = graphene.String() | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def mutate_and_get_payload(cls, input, context, info): |         def mutate_and_get_payload(cls, root, info, **input): | ||||||
|             # When using it in Django, context will be the request |             # When using it in Django, context will be the request | ||||||
|             files = context.FILES |             files = context.FILES | ||||||
|             # Or, if used in Flask, context will be the flask global request |             # Or, if used in Flask, context will be the flask global request | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ Example usage (taken from the `Starwars Relay example`_): | ||||||
|         name = graphene.String(description='The name of the ship.') |         name = graphene.String(description='The name of the ship.') | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def get_node(cls, id, context, info): |         def get_node(cls, info, id): | ||||||
|             return get_ship(id) |             return get_ship(id) | ||||||
| 
 | 
 | ||||||
| The ``id`` returned by the ``Ship`` type when you query it will be a | The ``id`` returned by the ``Ship`` type when you query it will be a | ||||||
|  | @ -55,7 +55,7 @@ Example of a custom node: | ||||||
|             return '{}:{}'.format(type, id) |             return '{}:{}'.format(type, id) | ||||||
| 
 | 
 | ||||||
|         @staticmethod |         @staticmethod | ||||||
|         def get_node_from_global_id(global_id, context, info, only_type=None): |         def get_node_from_global_id(info global_id, only_type=None): | ||||||
|             type, id = global_id.split(':') |             type, id = global_id.split(':') | ||||||
|             if only_node: |             if only_node: | ||||||
|                 # We assure that the node type that we want to retrieve |                 # We assure that the node type that we want to retrieve | ||||||
|  |  | ||||||
|  | @ -13,15 +13,14 @@ This example defines a Mutation: | ||||||
|     import graphene |     import graphene | ||||||
| 
 | 
 | ||||||
|     class CreatePerson(graphene.Mutation): |     class CreatePerson(graphene.Mutation): | ||||||
|         class Input: |         class Arguments: | ||||||
|             name = graphene.String() |             name = graphene.String() | ||||||
| 
 | 
 | ||||||
|         ok = graphene.Boolean() |         ok = graphene.Boolean() | ||||||
|         person = graphene.Field(lambda: Person) |         person = graphene.Field(lambda: Person) | ||||||
| 
 | 
 | ||||||
|         @staticmethod |         def mutate(self, name): | ||||||
|         def mutate(root, args, context, info): |             person = Person(name=name) | ||||||
|             person = Person(name=args.get('name')) |  | ||||||
|             ok = True |             ok = True | ||||||
|             return CreatePerson(person=person, ok=ok) |             return CreatePerson(person=person, ok=ok) | ||||||
| 
 | 
 | ||||||
|  | @ -90,30 +89,29 @@ InputFields are used in mutations to allow nested input data for mutations | ||||||
| To use an InputField you define an InputObjectType that specifies the structure of your input data | To use an InputField you define an InputObjectType that specifies the structure of your input data | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| .. code:: python | .. code:: python | ||||||
| 
 | 
 | ||||||
|     import graphene |     import graphene | ||||||
| 
 | 
 | ||||||
|     class PersonInput(graphene.InputObjectType): |     class PersonInput(graphene.InputObjectType): | ||||||
|         name = graphene.String() |         name = graphene.String(required=True) | ||||||
|         age = graphene.Int() |         age = graphene.Int(required=True) | ||||||
| 
 | 
 | ||||||
|     class CreatePerson(graphene.Mutation): |     class CreatePerson(graphene.Mutation): | ||||||
|         class Input: |         class Arguments: | ||||||
|             person_data = graphene.Argument(PersonInput) |             person_data = PersonInput(required=True) | ||||||
| 
 | 
 | ||||||
|         person = graphene.Field(lambda: Person) |         person = graphene.Field(Person) | ||||||
| 
 | 
 | ||||||
|         @staticmethod |         @staticmethod | ||||||
|         def mutate(root, args, context, info): |         def mutate(root, person_data=None): | ||||||
|             p_data = args.get('person_data') |  | ||||||
| 
 |  | ||||||
|             name = p_data.get('name') |             name = p_data.get('name') | ||||||
|             age = p_data.get('age') |             age = p_data.get('age') | ||||||
| 
 | 
 | ||||||
|             person = Person(name=name, age=age) |             person = Person( | ||||||
|  |                 name=person_data.name, | ||||||
|  |                 age=person_data.age | ||||||
|  |             ) | ||||||
|             return CreatePerson(person=person) |             return CreatePerson(person=person) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ This example model defines a Person, with a first and a last name: | ||||||
|         last_name = graphene.String() |         last_name = graphene.String() | ||||||
|         full_name = graphene.String() |         full_name = graphene.String() | ||||||
| 
 | 
 | ||||||
|         def resolve_full_name(self, args, context, info): |         def resolve_full_name(self, info): | ||||||
|             return '{} {}'.format(self.first_name, self.last_name) |             return '{} {}'.format(self.first_name, self.last_name) | ||||||
| 
 | 
 | ||||||
| **first\_name** and **last\_name** are fields of the ObjectType. Each | **first\_name** and **last\_name** are fields of the ObjectType. Each | ||||||
|  | @ -71,8 +71,7 @@ method in the class. | ||||||
|     class Query(graphene.ObjectType): |     class Query(graphene.ObjectType): | ||||||
|         reverse = graphene.String(word=graphene.String()) |         reverse = graphene.String(word=graphene.String()) | ||||||
| 
 | 
 | ||||||
|         def resolve_reverse(self, args, context, info): |         def resolve_reverse(self, info, word): | ||||||
|             word = args.get('word') |  | ||||||
|             return word[::-1] |             return word[::-1] | ||||||
| 
 | 
 | ||||||
| Resolvers outside the class | Resolvers outside the class | ||||||
|  | @ -84,8 +83,7 @@ A field can use a custom resolver from outside the class: | ||||||
| 
 | 
 | ||||||
|     import graphene |     import graphene | ||||||
| 
 | 
 | ||||||
|     def reverse(root, args, context, info): |     def reverse(root, info, word): | ||||||
|         word = args.get('word') |  | ||||||
|         return word[::-1] |         return word[::-1] | ||||||
| 
 | 
 | ||||||
|     class Query(graphene.ObjectType): |     class Query(graphene.ObjectType): | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								docs/types/unions.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								docs/types/unions.rst
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | ||||||
|  | Unions | ||||||
|  | ====== | ||||||
|  | 
 | ||||||
|  | Union types are very similar to interfaces, but they don't get | ||||||
|  | to specify any common fields between the types. | ||||||
|  | 
 | ||||||
|  | The basics: | ||||||
|  | 
 | ||||||
|  | - Each Union is a Python class that inherits from ``graphene.Union``. | ||||||
|  | - Unions don't have any fields on it, just links to the possible objecttypes. | ||||||
|  | 
 | ||||||
|  | Quick example | ||||||
|  | ------------- | ||||||
|  | 
 | ||||||
|  | This example model defines a ``Character`` interface with a name. ``Human`` | ||||||
|  | and ``Droid`` are two implementations of that interface. | ||||||
|  | 
 | ||||||
|  | .. code:: python | ||||||
|  | 
 | ||||||
|  |     import graphene | ||||||
|  | 
 | ||||||
|  |     class Human(graphene.ObjectType): | ||||||
|  |         name = graphene.String() | ||||||
|  |         born_in = graphene.String() | ||||||
|  | 
 | ||||||
|  |     class Droid(graphene.ObjectType): | ||||||
|  |         name = graphene.String() | ||||||
|  |         primary_function = graphene.String() | ||||||
|  | 
 | ||||||
|  |     class Starship(graphene.ObjectType): | ||||||
|  |         name = graphene.String() | ||||||
|  |         length = graphene.Int() | ||||||
|  | 
 | ||||||
|  |     class SearchResult(graphene.Union): | ||||||
|  |         class Meta: | ||||||
|  |             types = (Human, Droid, Starship) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Wherever we return a SearchResult type in our schema, we might get a Human, a Droid, or a Starship. | ||||||
|  | Note that members of a union type need to be concrete object types; | ||||||
|  | you can't create a union type out of interfaces or other unions. | ||||||
|  | 
 | ||||||
|  | The above types have the following representation in a schema: | ||||||
|  | 
 | ||||||
|  | .. code:: | ||||||
|  | 
 | ||||||
|  |     type Droid { | ||||||
|  |       name: String | ||||||
|  |       primaryFunction: String | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     type Human { | ||||||
|  |       name: String | ||||||
|  |       bornIn: String | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     type Ship { | ||||||
|  |       name: String | ||||||
|  |       length: Int | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     union SearchResult = Human | Droid | Starship | ||||||
|  | 
 | ||||||
|  | @ -11,10 +11,9 @@ class Address(graphene.ObjectType): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Query(graphene.ObjectType): | class Query(graphene.ObjectType): | ||||||
|     address = graphene.Field(Address, geo=GeoInput()) |     address = graphene.Field(Address, geo=GeoInput(required=True)) | ||||||
| 
 | 
 | ||||||
|     def resolve_address(self, args, context, info): |     def resolve_address(self, info, geo): | ||||||
|         geo = args.get('geo') |  | ||||||
|         return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng'))) |         return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng'))) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,8 +9,9 @@ class User(graphene.ObjectType): | ||||||
| class Query(graphene.ObjectType): | class Query(graphene.ObjectType): | ||||||
|     me = graphene.Field(User) |     me = graphene.Field(User) | ||||||
| 
 | 
 | ||||||
|     def resolve_me(self, args, context, info): |     def resolve_me(self, info): | ||||||
|         return context['user'] |         return info.context['user'] | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| schema = graphene.Schema(query=Query) | schema = graphene.Schema(query=Query) | ||||||
| query = ''' | query = ''' | ||||||
|  |  | ||||||
|  | @ -11,9 +11,10 @@ class Query(graphene.ObjectType): | ||||||
| 
 | 
 | ||||||
|     patron = graphene.Field(Patron) |     patron = graphene.Field(Patron) | ||||||
| 
 | 
 | ||||||
|     def resolve_patron(self, args, context, info): |     def resolve_patron(self, info): | ||||||
|         return Patron(id=1, name='Syrus', age=27) |         return Patron(id=1, name='Syrus', age=27) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| schema = graphene.Schema(query=Query) | schema = graphene.Schema(query=Query) | ||||||
| query = ''' | query = ''' | ||||||
|     query something{ |     query something{ | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import graphene | import graphene | ||||||
| from graphene import resolve_only_args |  | ||||||
| 
 | 
 | ||||||
| from .data import get_character, get_droid, get_hero, get_human | from .data import get_character, get_droid, get_hero, get_human | ||||||
| 
 | 
 | ||||||
|  | @ -16,7 +15,7 @@ class Character(graphene.Interface): | ||||||
|     friends = graphene.List(lambda: Character) |     friends = graphene.List(lambda: Character) | ||||||
|     appears_in = graphene.List(Episode) |     appears_in = graphene.List(Episode) | ||||||
| 
 | 
 | ||||||
|     def resolve_friends(self, args, *_): |     def resolve_friends(self, info): | ||||||
|         # The character friends is a list of strings |         # The character friends is a list of strings | ||||||
|         return [get_character(f) for f in self.friends] |         return [get_character(f) for f in self.friends] | ||||||
| 
 | 
 | ||||||
|  | @ -46,16 +45,13 @@ class Query(graphene.ObjectType): | ||||||
|                            id=graphene.String() |                            id=graphene.String() | ||||||
|                            ) |                            ) | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     def resolve_hero(self, info, episode=None): | ||||||
|     def resolve_hero(self, episode=None): |  | ||||||
|         return get_hero(episode) |         return get_hero(episode) | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     def resolve_human(self, info, id): | ||||||
|     def resolve_human(self, id): |  | ||||||
|         return get_human(id) |         return get_human(id) | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     def resolve_droid(self, info, id): | ||||||
|     def resolve_droid(self, id): |  | ||||||
|         return get_droid(id) |         return get_droid(id) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from graphene.test import Client | from graphene.test import Client | ||||||
|  | 
 | ||||||
| from ..data import setup | from ..data import setup | ||||||
| from ..schema import schema | from ..schema import schema | ||||||
| 
 | 
 | ||||||
|  | @ -6,6 +7,7 @@ setup() | ||||||
| 
 | 
 | ||||||
| client = Client(schema) | client = Client(schema) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def test_hero_name_query(snapshot): | def test_hero_name_query(snapshot): | ||||||
|     query = ''' |     query = ''' | ||||||
|         query HeroNameQuery { |         query HeroNameQuery { | ||||||
|  | @ -17,7 +19,6 @@ def test_hero_name_query(snapshot): | ||||||
|     snapshot.assert_match(client.execute(query)) |     snapshot.assert_match(client.execute(query)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def test_hero_name_and_friends_query(snapshot): | def test_hero_name_and_friends_query(snapshot): | ||||||
|     query = ''' |     query = ''' | ||||||
|         query HeroNameAndFriendsQuery { |         query HeroNameAndFriendsQuery { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import graphene | import graphene | ||||||
| from graphene import relay, resolve_only_args | from graphene import relay | ||||||
| 
 | 
 | ||||||
| from .data import create_ship, get_empire, get_faction, get_rebels, get_ship | from .data import create_ship, get_empire, get_faction, get_rebels, get_ship | ||||||
| 
 | 
 | ||||||
|  | @ -13,10 +13,16 @@ class Ship(graphene.ObjectType): | ||||||
|     name = graphene.String(description='The name of the ship.') |     name = graphene.String(description='The name of the ship.') | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_node(cls, id, context, info): |     def get_node(cls, info, id): | ||||||
|         return get_ship(id) |         return get_ship(id) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class ShipConnection(relay.Connection): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         node = Ship | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class Faction(graphene.ObjectType): | class Faction(graphene.ObjectType): | ||||||
|     '''A faction in the Star Wars saga''' |     '''A faction in the Star Wars saga''' | ||||||
| 
 | 
 | ||||||
|  | @ -24,15 +30,14 @@ class Faction(graphene.ObjectType): | ||||||
|         interfaces = (relay.Node, ) |         interfaces = (relay.Node, ) | ||||||
| 
 | 
 | ||||||
|     name = graphene.String(description='The name of the faction.') |     name = graphene.String(description='The name of the faction.') | ||||||
|     ships = relay.ConnectionField(Ship, description='The ships used by the faction.') |     ships = relay.ConnectionField(ShipConnection, description='The ships used by the faction.') | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     def resolve_ships(self, info, **args): | ||||||
|     def resolve_ships(self, **args): |  | ||||||
|         # Transform the instance ship_ids into real instances |         # Transform the instance ship_ids into real instances | ||||||
|         return [get_ship(ship_id) for ship_id in self.ships] |         return [get_ship(ship_id) for ship_id in self.ships] | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_node(cls, id, context, info): |     def get_node(cls, info, id): | ||||||
|         return get_faction(id) |         return get_faction(id) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -46,9 +51,7 @@ class IntroduceShip(relay.ClientIDMutation): | ||||||
|     faction = graphene.Field(Faction) |     faction = graphene.Field(Faction) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def mutate_and_get_payload(cls, input, context, info): |     def mutate_and_get_payload(cls, root, info, ship_name, faction_id, client_mutation_id=None): | ||||||
|         ship_name = input.get('ship_name') |  | ||||||
|         faction_id = input.get('faction_id') |  | ||||||
|         ship = create_ship(ship_name, faction_id) |         ship = create_ship(ship_name, faction_id) | ||||||
|         faction = get_faction(faction_id) |         faction = get_faction(faction_id) | ||||||
|         return IntroduceShip(ship=ship, faction=faction) |         return IntroduceShip(ship=ship, faction=faction) | ||||||
|  | @ -59,12 +62,10 @@ class Query(graphene.ObjectType): | ||||||
|     empire = graphene.Field(Faction) |     empire = graphene.Field(Faction) | ||||||
|     node = relay.Node.Field() |     node = relay.Node.Field() | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     def resolve_rebels(self, info): | ||||||
|     def resolve_rebels(self): |  | ||||||
|         return get_rebels() |         return get_rebels() | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     def resolve_empire(self, info): | ||||||
|     def resolve_empire(self): |  | ||||||
|         return get_empire() |         return get_empire() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -51,3 +51,63 @@ snapshots['test_correctly_refetches_xwing 1'] = { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | snapshots['test_str_schema 1'] = '''schema { | ||||||
|  |   query: Query | ||||||
|  |   mutation: Mutation | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Faction implements Node { | ||||||
|  |   id: ID! | ||||||
|  |   name: String | ||||||
|  |   ships(before: String, after: String, first: Int, last: Int): ShipConnection | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input IntroduceShipInput { | ||||||
|  |   shipName: String! | ||||||
|  |   factionId: String! | ||||||
|  |   clientMutationId: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IntroduceShipPayload { | ||||||
|  |   ship: Ship | ||||||
|  |   faction: Faction | ||||||
|  |   clientMutationId: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Mutation { | ||||||
|  |   introduceShip(input: IntroduceShipInput!): IntroduceShipPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface Node { | ||||||
|  |   id: ID! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PageInfo { | ||||||
|  |   hasNextPage: Boolean! | ||||||
|  |   hasPreviousPage: Boolean! | ||||||
|  |   startCursor: String | ||||||
|  |   endCursor: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Query { | ||||||
|  |   rebels: Faction | ||||||
|  |   empire: Faction | ||||||
|  |   node(id: ID!): Node | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Ship implements Node { | ||||||
|  |   id: ID! | ||||||
|  |   name: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ShipConnection { | ||||||
|  |   pageInfo: PageInfo! | ||||||
|  |   edges: [ShipEdge]! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ShipEdge { | ||||||
|  |   node: Ship | ||||||
|  |   cursor: String! | ||||||
|  | } | ||||||
|  | ''' | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from graphene.test import Client | from graphene.test import Client | ||||||
|  | 
 | ||||||
| from ..data import setup | from ..data import setup | ||||||
| from ..schema import schema | from ..schema import schema | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from graphene.test import Client | from graphene.test import Client | ||||||
|  | 
 | ||||||
| from ..data import setup | from ..data import setup | ||||||
| from ..schema import schema | from ..schema import schema | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from graphene.test import Client | from graphene.test import Client | ||||||
|  | 
 | ||||||
| from ..data import setup | from ..data import setup | ||||||
| from ..schema import schema | from ..schema import schema | ||||||
| 
 | 
 | ||||||
|  | @ -7,66 +8,8 @@ setup() | ||||||
| client = Client(schema) | client = Client(schema) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_str_schema(): | def test_str_schema(snapshot): | ||||||
|     assert str(schema) == '''schema { |     snapshot.assert_match(str(schema)) | ||||||
|   query: Query |  | ||||||
|   mutation: Mutation |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Faction implements Node { |  | ||||||
|   id: ID! |  | ||||||
|   name: String |  | ||||||
|   ships(before: String, after: String, first: Int, last: Int): ShipConnection |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input IntroduceShipInput { |  | ||||||
|   shipName: String! |  | ||||||
|   factionId: String! |  | ||||||
|   clientMutationId: String |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type IntroduceShipPayload { |  | ||||||
|   ship: Ship |  | ||||||
|   faction: Faction |  | ||||||
|   clientMutationId: String |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Mutation { |  | ||||||
|   introduceShip(input: IntroduceShipInput!): IntroduceShipPayload |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| interface Node { |  | ||||||
|   id: ID! |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type PageInfo { |  | ||||||
|   hasNextPage: Boolean! |  | ||||||
|   hasPreviousPage: Boolean! |  | ||||||
|   startCursor: String |  | ||||||
|   endCursor: String |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Query { |  | ||||||
|   rebels: Faction |  | ||||||
|   empire: Faction |  | ||||||
|   node(id: ID!): Node |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Ship implements Node { |  | ||||||
|   id: ID! |  | ||||||
|   name: String |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type ShipConnection { |  | ||||||
|   pageInfo: PageInfo! |  | ||||||
|   edges: [ShipEdge]! |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type ShipEdge { |  | ||||||
|   node: Ship |  | ||||||
|   cursor: String! |  | ||||||
| } |  | ||||||
| ''' |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_correctly_fetches_id_name_rebels(snapshot): | def test_correctly_fetches_id_name_rebels(snapshot): | ||||||
|  |  | ||||||
|  | @ -1,22 +1,6 @@ | ||||||
| from .pyutils.version import get_version | from .pyutils.version import get_version | ||||||
| 
 | 
 | ||||||
| 
 | from .types import ( | ||||||
| try: |  | ||||||
|     # This variable is injected in the __builtins__ by the build |  | ||||||
|     # process. It used to enable importing subpackages when |  | ||||||
|     # the required packages are not installed |  | ||||||
|     __SETUP__ |  | ||||||
| except NameError: |  | ||||||
|     __SETUP__ = False |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| VERSION = (1, 4, 1, 'final', 0) |  | ||||||
| 
 |  | ||||||
| __version__ = get_version(VERSION) |  | ||||||
| 
 |  | ||||||
| if not __SETUP__: |  | ||||||
| 
 |  | ||||||
|     from .types import ( |  | ||||||
|     AbstractType, |     AbstractType, | ||||||
|     ObjectType, |     ObjectType, | ||||||
|     InputObjectType, |     InputObjectType, | ||||||
|  | @ -27,13 +11,17 @@ if not __SETUP__: | ||||||
|     Schema, |     Schema, | ||||||
|     Scalar, |     Scalar, | ||||||
|     String, ID, Int, Float, Boolean, |     String, ID, Int, Float, Boolean, | ||||||
|  |     JSONString, | ||||||
|  |     UUID, | ||||||
|     List, NonNull, |     List, NonNull, | ||||||
|     Enum, |     Enum, | ||||||
|     Argument, |     Argument, | ||||||
|     Dynamic, |     Dynamic, | ||||||
|     Union, |     Union, | ||||||
|     ) |     Context, | ||||||
|     from .relay import ( |     ResolveInfo | ||||||
|  | ) | ||||||
|  | from .relay import ( | ||||||
|     Node, |     Node, | ||||||
|     is_node, |     is_node, | ||||||
|     GlobalID, |     GlobalID, | ||||||
|  | @ -41,12 +29,17 @@ if not __SETUP__: | ||||||
|     Connection, |     Connection, | ||||||
|     ConnectionField, |     ConnectionField, | ||||||
|     PageInfo |     PageInfo | ||||||
|     ) | ) | ||||||
|     from .utils.resolve_only_args import resolve_only_args | from .utils.resolve_only_args import resolve_only_args | ||||||
|     from .utils.module_loading import lazy_import | from .utils.module_loading import lazy_import | ||||||
| 
 | 
 | ||||||
|     __all__ = [ | 
 | ||||||
|         'AbstractType', | VERSION = (2, 0, 0, 'alpha', 0) | ||||||
|  | 
 | ||||||
|  | __version__ = get_version(VERSION) | ||||||
|  | 
 | ||||||
|  | __all__ = [ | ||||||
|  |     '__version__', | ||||||
|     'ObjectType', |     'ObjectType', | ||||||
|     'InputObjectType', |     'InputObjectType', | ||||||
|     'Interface', |     'Interface', | ||||||
|  | @ -61,6 +54,8 @@ if not __SETUP__: | ||||||
|     'Float', |     'Float', | ||||||
|     'Enum', |     'Enum', | ||||||
|     'Boolean', |     'Boolean', | ||||||
|  |     'JSONString', | ||||||
|  |     'UUID', | ||||||
|     'List', |     'List', | ||||||
|     'NonNull', |     'NonNull', | ||||||
|     'Argument', |     'Argument', | ||||||
|  | @ -75,4 +70,9 @@ if not __SETUP__: | ||||||
|     'ConnectionField', |     'ConnectionField', | ||||||
|     'PageInfo', |     'PageInfo', | ||||||
|     'lazy_import', |     'lazy_import', | ||||||
|     ] |     'Context', | ||||||
|  |     'ResolveInfo', | ||||||
|  | 
 | ||||||
|  |     # Deprecated | ||||||
|  |     'AbstractType', | ||||||
|  | ] | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								graphene/pyutils/compat.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								graphene/pyutils/compat.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | from __future__ import absolute_import | ||||||
|  | import six | ||||||
|  | 
 | ||||||
|  | try: | ||||||
|  |     from enum import Enum | ||||||
|  | except ImportError: | ||||||
|  |     from .enum import Enum | ||||||
|  | 
 | ||||||
|  | try: | ||||||
|  |     from inspect import signature | ||||||
|  | except ImportError: | ||||||
|  |     from .signature import signature | ||||||
|  | 
 | ||||||
|  | if six.PY2: | ||||||
|  |     def func_name(func): | ||||||
|  |         return func.func_name | ||||||
|  | else: | ||||||
|  |     def func_name(func): | ||||||
|  |         return func.__name__ | ||||||
							
								
								
									
										19
									
								
								graphene/pyutils/init_subclass.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								graphene/pyutils/init_subclass.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | is_init_subclass_available = hasattr(object, '__init_subclass__') | ||||||
|  | 
 | ||||||
|  | if not is_init_subclass_available: | ||||||
|  |     class InitSubclassMeta(type): | ||||||
|  |         """Metaclass that implements PEP 487 protocol""" | ||||||
|  |         def __new__(cls, name, bases, ns, **kwargs): | ||||||
|  |             __init_subclass__ = ns.pop('__init_subclass__', None) | ||||||
|  |             if __init_subclass__: | ||||||
|  |                 __init_subclass__ = classmethod(__init_subclass__) | ||||||
|  |                 ns['__init_subclass__'] = __init_subclass__ | ||||||
|  |             return super(InitSubclassMeta, cls).__new__(cls, name, bases, ns, **kwargs) | ||||||
|  | 
 | ||||||
|  |         def __init__(cls, name, bases, ns, **kwargs): | ||||||
|  |             super(InitSubclassMeta, cls).__init__(name, bases, ns) | ||||||
|  |             super_class = super(cls, cls) | ||||||
|  |             if hasattr(super_class, '__init_subclass__'): | ||||||
|  |                 super_class.__init_subclass__.__func__(cls, **kwargs) | ||||||
|  | else: | ||||||
|  |     InitSubclassMeta = type  # type: ignore | ||||||
							
								
								
									
										808
									
								
								graphene/pyutils/signature.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										808
									
								
								graphene/pyutils/signature.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,808 @@ | ||||||
|  | # Copyright 2001-2013 Python Software Foundation; All Rights Reserved | ||||||
|  | """Function signature objects for callables | ||||||
|  | Back port of Python 3.3's function signature tools from the inspect module, | ||||||
|  | modified to be compatible with Python 2.7 and 3.2+. | ||||||
|  | """ | ||||||
|  | from __future__ import absolute_import, division, print_function | ||||||
|  | import itertools | ||||||
|  | import functools | ||||||
|  | import re | ||||||
|  | import types | ||||||
|  | 
 | ||||||
|  | from collections import OrderedDict | ||||||
|  | 
 | ||||||
|  | __version__ = "0.4" | ||||||
|  | 
 | ||||||
|  | __all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature'] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | _WrapperDescriptor = type(type.__call__) | ||||||
|  | _MethodWrapper = type(all.__call__) | ||||||
|  | 
 | ||||||
|  | _NonUserDefinedCallables = (_WrapperDescriptor, | ||||||
|  |                             _MethodWrapper, | ||||||
|  |                             types.BuiltinFunctionType) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def formatannotation(annotation, base_module=None): | ||||||
|  |     if isinstance(annotation, type): | ||||||
|  |         if annotation.__module__ in ('builtins', '__builtin__', base_module): | ||||||
|  |             return annotation.__name__ | ||||||
|  |         return annotation.__module__+'.'+annotation.__name__ | ||||||
|  |     return repr(annotation) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _get_user_defined_method(cls, method_name, *nested): | ||||||
|  |     try: | ||||||
|  |         if cls is type: | ||||||
|  |             return | ||||||
|  |         meth = getattr(cls, method_name) | ||||||
|  |         for name in nested: | ||||||
|  |             meth = getattr(meth, name, meth) | ||||||
|  |     except AttributeError: | ||||||
|  |         return | ||||||
|  |     else: | ||||||
|  |         if not isinstance(meth, _NonUserDefinedCallables): | ||||||
|  |             # Once '__signature__' will be added to 'C'-level | ||||||
|  |             # callables, this check won't be necessary | ||||||
|  |             return meth | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def signature(obj): | ||||||
|  |     '''Get a signature object for the passed callable.''' | ||||||
|  | 
 | ||||||
|  |     if not callable(obj): | ||||||
|  |         raise TypeError('{0!r} is not a callable object'.format(obj)) | ||||||
|  | 
 | ||||||
|  |     if isinstance(obj, types.MethodType): | ||||||
|  |         sig = signature(obj.__func__) | ||||||
|  |         if obj.__self__ is None: | ||||||
|  |             # Unbound method: the first parameter becomes positional-only | ||||||
|  |             if sig.parameters: | ||||||
|  |                 first = sig.parameters.values()[0].replace( | ||||||
|  |                     kind=_POSITIONAL_ONLY) | ||||||
|  |                 return sig.replace( | ||||||
|  |                     parameters=(first,) + tuple(sig.parameters.values())[1:]) | ||||||
|  |             else: | ||||||
|  |                 return sig | ||||||
|  |         else: | ||||||
|  |             # In this case we skip the first parameter of the underlying | ||||||
|  |             # function (usually `self` or `cls`). | ||||||
|  |             return sig.replace(parameters=tuple(sig.parameters.values())[1:]) | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         sig = obj.__signature__ | ||||||
|  |     except AttributeError: | ||||||
|  |         pass | ||||||
|  |     else: | ||||||
|  |         if sig is not None: | ||||||
|  |             return sig | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         # Was this function wrapped by a decorator? | ||||||
|  |         wrapped = obj.__wrapped__ | ||||||
|  |     except AttributeError: | ||||||
|  |         pass | ||||||
|  |     else: | ||||||
|  |         return signature(wrapped) | ||||||
|  | 
 | ||||||
|  |     if isinstance(obj, types.FunctionType): | ||||||
|  |         return Signature.from_function(obj) | ||||||
|  | 
 | ||||||
|  |     if isinstance(obj, functools.partial): | ||||||
|  |         sig = signature(obj.func) | ||||||
|  | 
 | ||||||
|  |         new_params = OrderedDict(sig.parameters.items()) | ||||||
|  | 
 | ||||||
|  |         partial_args = obj.args or () | ||||||
|  |         partial_keywords = obj.keywords or {} | ||||||
|  |         try: | ||||||
|  |             ba = sig.bind_partial(*partial_args, **partial_keywords) | ||||||
|  |         except TypeError as ex: | ||||||
|  |             msg = 'partial object {0!r} has incorrect arguments'.format(obj) | ||||||
|  |             raise ValueError(msg) | ||||||
|  | 
 | ||||||
|  |         for arg_name, arg_value in ba.arguments.items(): | ||||||
|  |             param = new_params[arg_name] | ||||||
|  |             if arg_name in partial_keywords: | ||||||
|  |                 # We set a new default value, because the following code | ||||||
|  |                 # is correct: | ||||||
|  |                 # | ||||||
|  |                 #   >>> def foo(a): print(a) | ||||||
|  |                 #   >>> print(partial(partial(foo, a=10), a=20)()) | ||||||
|  |                 #   20 | ||||||
|  |                 #   >>> print(partial(partial(foo, a=10), a=20)(a=30)) | ||||||
|  |                 #   30 | ||||||
|  |                 # | ||||||
|  |                 # So, with 'partial' objects, passing a keyword argument is | ||||||
|  |                 # like setting a new default value for the corresponding | ||||||
|  |                 # parameter | ||||||
|  |                 # | ||||||
|  |                 # We also mark this parameter with '_partial_kwarg' | ||||||
|  |                 # flag.  Later, in '_bind', the 'default' value of this | ||||||
|  |                 # parameter will be added to 'kwargs', to simulate | ||||||
|  |                 # the 'functools.partial' real call. | ||||||
|  |                 new_params[arg_name] = param.replace(default=arg_value, | ||||||
|  |                                                      _partial_kwarg=True) | ||||||
|  | 
 | ||||||
|  |             elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and | ||||||
|  |                             not param._partial_kwarg): | ||||||
|  |                 new_params.pop(arg_name) | ||||||
|  | 
 | ||||||
|  |         return sig.replace(parameters=new_params.values()) | ||||||
|  | 
 | ||||||
|  |     sig = None | ||||||
|  |     if isinstance(obj, type): | ||||||
|  |         # obj is a class or a metaclass | ||||||
|  | 
 | ||||||
|  |         # First, let's see if it has an overloaded __call__ defined | ||||||
|  |         # in its metaclass | ||||||
|  |         call = _get_user_defined_method(type(obj), '__call__') | ||||||
|  |         if call is not None: | ||||||
|  |             sig = signature(call) | ||||||
|  |         else: | ||||||
|  |             # Now we check if the 'obj' class has a '__new__' method | ||||||
|  |             new = _get_user_defined_method(obj, '__new__') | ||||||
|  |             if new is not None: | ||||||
|  |                 sig = signature(new) | ||||||
|  |             else: | ||||||
|  |                 # Finally, we should have at least __init__ implemented | ||||||
|  |                 init = _get_user_defined_method(obj, '__init__') | ||||||
|  |                 if init is not None: | ||||||
|  |                     sig = signature(init) | ||||||
|  |     elif not isinstance(obj, _NonUserDefinedCallables): | ||||||
|  |         # An object with __call__ | ||||||
|  |         # We also check that the 'obj' is not an instance of | ||||||
|  |         # _WrapperDescriptor or _MethodWrapper to avoid | ||||||
|  |         # infinite recursion (and even potential segfault) | ||||||
|  |         call = _get_user_defined_method(type(obj), '__call__', 'im_func') | ||||||
|  |         if call is not None: | ||||||
|  |             sig = signature(call) | ||||||
|  | 
 | ||||||
|  |     if sig is not None: | ||||||
|  |         # For classes and objects we skip the first parameter of their | ||||||
|  |         # __call__, __new__, or __init__ methods | ||||||
|  |         return sig.replace(parameters=tuple(sig.parameters.values())[1:]) | ||||||
|  | 
 | ||||||
|  |     if isinstance(obj, types.BuiltinFunctionType): | ||||||
|  |         # Raise a nicer error message for builtins | ||||||
|  |         msg = 'no signature found for builtin function {0!r}'.format(obj) | ||||||
|  |         raise ValueError(msg) | ||||||
|  | 
 | ||||||
|  |     raise ValueError('callable {0!r} is not supported by signature'.format(obj)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class _void(object): | ||||||
|  |     '''A private marker - used in Parameter & Signature''' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class _empty(object): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class _ParameterKind(int): | ||||||
|  |     def __new__(self, *args, **kwargs): | ||||||
|  |         obj = int.__new__(self, *args) | ||||||
|  |         obj._name = kwargs['name'] | ||||||
|  |         return obj | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return self._name | ||||||
|  | 
 | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '<_ParameterKind: {0!r}>'.format(self._name) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | _POSITIONAL_ONLY        = _ParameterKind(0, name='POSITIONAL_ONLY') | ||||||
|  | _POSITIONAL_OR_KEYWORD  = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD') | ||||||
|  | _VAR_POSITIONAL         = _ParameterKind(2, name='VAR_POSITIONAL') | ||||||
|  | _KEYWORD_ONLY           = _ParameterKind(3, name='KEYWORD_ONLY') | ||||||
|  | _VAR_KEYWORD            = _ParameterKind(4, name='VAR_KEYWORD') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Parameter(object): | ||||||
|  |     '''Represents a parameter in a function signature. | ||||||
|  |     Has the following public attributes: | ||||||
|  |     * name : str | ||||||
|  |         The name of the parameter as a string. | ||||||
|  |     * default : object | ||||||
|  |         The default value for the parameter if specified.  If the | ||||||
|  |         parameter has no default value, this attribute is not set. | ||||||
|  |     * annotation | ||||||
|  |         The annotation for the parameter if specified.  If the | ||||||
|  |         parameter has no annotation, this attribute is not set. | ||||||
|  |     * kind : str | ||||||
|  |         Describes how argument values are bound to the parameter. | ||||||
|  |         Possible values: `Parameter.POSITIONAL_ONLY`, | ||||||
|  |         `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`, | ||||||
|  |         `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg') | ||||||
|  | 
 | ||||||
|  |     POSITIONAL_ONLY         = _POSITIONAL_ONLY | ||||||
|  |     POSITIONAL_OR_KEYWORD   = _POSITIONAL_OR_KEYWORD | ||||||
|  |     VAR_POSITIONAL          = _VAR_POSITIONAL | ||||||
|  |     KEYWORD_ONLY            = _KEYWORD_ONLY | ||||||
|  |     VAR_KEYWORD             = _VAR_KEYWORD | ||||||
|  | 
 | ||||||
|  |     empty = _empty | ||||||
|  | 
 | ||||||
|  |     def __init__(self, name, kind, default=_empty, annotation=_empty, | ||||||
|  |                  _partial_kwarg=False): | ||||||
|  | 
 | ||||||
|  |         if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD, | ||||||
|  |                         _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD): | ||||||
|  |             raise ValueError("invalid value for 'Parameter.kind' attribute") | ||||||
|  |         self._kind = kind | ||||||
|  | 
 | ||||||
|  |         if default is not _empty: | ||||||
|  |             if kind in (_VAR_POSITIONAL, _VAR_KEYWORD): | ||||||
|  |                 msg = '{0} parameters cannot have default values'.format(kind) | ||||||
|  |                 raise ValueError(msg) | ||||||
|  |         self._default = default | ||||||
|  |         self._annotation = annotation | ||||||
|  | 
 | ||||||
|  |         if name is None: | ||||||
|  |             if kind != _POSITIONAL_ONLY: | ||||||
|  |                 raise ValueError("None is not a valid name for a " | ||||||
|  |                                  "non-positional-only parameter") | ||||||
|  |             self._name = name | ||||||
|  |         else: | ||||||
|  |             name = str(name) | ||||||
|  |             if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I): | ||||||
|  |                 msg = '{0!r} is not a valid parameter name'.format(name) | ||||||
|  |                 raise ValueError(msg) | ||||||
|  |             self._name = name | ||||||
|  | 
 | ||||||
|  |         self._partial_kwarg = _partial_kwarg | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def name(self): | ||||||
|  |         return self._name | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def default(self): | ||||||
|  |         return self._default | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def annotation(self): | ||||||
|  |         return self._annotation | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def kind(self): | ||||||
|  |         return self._kind | ||||||
|  | 
 | ||||||
|  |     def replace(self, name=_void, kind=_void, annotation=_void, | ||||||
|  |                 default=_void, _partial_kwarg=_void): | ||||||
|  |         '''Creates a customized copy of the Parameter.''' | ||||||
|  | 
 | ||||||
|  |         if name is _void: | ||||||
|  |             name = self._name | ||||||
|  | 
 | ||||||
|  |         if kind is _void: | ||||||
|  |             kind = self._kind | ||||||
|  | 
 | ||||||
|  |         if annotation is _void: | ||||||
|  |             annotation = self._annotation | ||||||
|  | 
 | ||||||
|  |         if default is _void: | ||||||
|  |             default = self._default | ||||||
|  | 
 | ||||||
|  |         if _partial_kwarg is _void: | ||||||
|  |             _partial_kwarg = self._partial_kwarg | ||||||
|  | 
 | ||||||
|  |         return type(self)(name, kind, default=default, annotation=annotation, | ||||||
|  |                           _partial_kwarg=_partial_kwarg) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         kind = self.kind | ||||||
|  | 
 | ||||||
|  |         formatted = self._name | ||||||
|  |         if kind == _POSITIONAL_ONLY: | ||||||
|  |             if formatted is None: | ||||||
|  |                 formatted = '' | ||||||
|  |             formatted = '<{0}>'.format(formatted) | ||||||
|  | 
 | ||||||
|  |         # Add annotation and default value | ||||||
|  |         if self._annotation is not _empty: | ||||||
|  |             formatted = '{0}:{1}'.format(formatted, | ||||||
|  |                                        formatannotation(self._annotation)) | ||||||
|  | 
 | ||||||
|  |         if self._default is not _empty: | ||||||
|  |             formatted = '{0}={1}'.format(formatted, repr(self._default)) | ||||||
|  | 
 | ||||||
|  |         if kind == _VAR_POSITIONAL: | ||||||
|  |             formatted = '*' + formatted | ||||||
|  |         elif kind == _VAR_KEYWORD: | ||||||
|  |             formatted = '**' + formatted | ||||||
|  | 
 | ||||||
|  |         return formatted | ||||||
|  | 
 | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__, | ||||||
|  |                                            id(self), self.name) | ||||||
|  | 
 | ||||||
|  |     def __hash__(self): | ||||||
|  |         msg = "unhashable type: '{0}'".format(self.__class__.__name__) | ||||||
|  |         raise TypeError(msg) | ||||||
|  | 
 | ||||||
|  |     def __eq__(self, other): | ||||||
|  |         return (issubclass(other.__class__, Parameter) and | ||||||
|  |                 self._name == other._name and | ||||||
|  |                 self._kind == other._kind and | ||||||
|  |                 self._default == other._default and | ||||||
|  |                 self._annotation == other._annotation) | ||||||
|  | 
 | ||||||
|  |     def __ne__(self, other): | ||||||
|  |         return not self.__eq__(other) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BoundArguments(object): | ||||||
|  |     '''Result of `Signature.bind` call.  Holds the mapping of arguments | ||||||
|  |     to the function's parameters. | ||||||
|  |     Has the following public attributes: | ||||||
|  |     * arguments : OrderedDict | ||||||
|  |         An ordered mutable mapping of parameters' names to arguments' values. | ||||||
|  |         Does not contain arguments' default values. | ||||||
|  |     * signature : Signature | ||||||
|  |         The Signature object that created this instance. | ||||||
|  |     * args : tuple | ||||||
|  |         Tuple of positional arguments values. | ||||||
|  |     * kwargs : dict | ||||||
|  |         Dict of keyword arguments values. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     def __init__(self, signature, arguments): | ||||||
|  |         self.arguments = arguments | ||||||
|  |         self._signature = signature | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def signature(self): | ||||||
|  |         return self._signature | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def args(self): | ||||||
|  |         args = [] | ||||||
|  |         for param_name, param in self._signature.parameters.items(): | ||||||
|  |             if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or | ||||||
|  |                                                     param._partial_kwarg): | ||||||
|  |                 # Keyword arguments mapped by 'functools.partial' | ||||||
|  |                 # (Parameter._partial_kwarg is True) are mapped | ||||||
|  |                 # in 'BoundArguments.kwargs', along with VAR_KEYWORD & | ||||||
|  |                 # KEYWORD_ONLY | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |             try: | ||||||
|  |                 arg = self.arguments[param_name] | ||||||
|  |             except KeyError: | ||||||
|  |                 # We're done here. Other arguments | ||||||
|  |                 # will be mapped in 'BoundArguments.kwargs' | ||||||
|  |                 break | ||||||
|  |             else: | ||||||
|  |                 if param.kind == _VAR_POSITIONAL: | ||||||
|  |                     # *args | ||||||
|  |                     args.extend(arg) | ||||||
|  |                 else: | ||||||
|  |                     # plain argument | ||||||
|  |                     args.append(arg) | ||||||
|  | 
 | ||||||
|  |         return tuple(args) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def kwargs(self): | ||||||
|  |         kwargs = {} | ||||||
|  |         kwargs_started = False | ||||||
|  |         for param_name, param in self._signature.parameters.items(): | ||||||
|  |             if not kwargs_started: | ||||||
|  |                 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or | ||||||
|  |                                                 param._partial_kwarg): | ||||||
|  |                     kwargs_started = True | ||||||
|  |                 else: | ||||||
|  |                     if param_name not in self.arguments: | ||||||
|  |                         kwargs_started = True | ||||||
|  |                         continue | ||||||
|  | 
 | ||||||
|  |             if not kwargs_started: | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|  |             try: | ||||||
|  |                 arg = self.arguments[param_name] | ||||||
|  |             except KeyError: | ||||||
|  |                 pass | ||||||
|  |             else: | ||||||
|  |                 if param.kind == _VAR_KEYWORD: | ||||||
|  |                     # **kwargs | ||||||
|  |                     kwargs.update(arg) | ||||||
|  |                 else: | ||||||
|  |                     # plain keyword argument | ||||||
|  |                     kwargs[param_name] = arg | ||||||
|  | 
 | ||||||
|  |         return kwargs | ||||||
|  | 
 | ||||||
|  |     def __hash__(self): | ||||||
|  |         msg = "unhashable type: '{0}'".format(self.__class__.__name__) | ||||||
|  |         raise TypeError(msg) | ||||||
|  | 
 | ||||||
|  |     def __eq__(self, other): | ||||||
|  |         return (issubclass(other.__class__, BoundArguments) and | ||||||
|  |                 self.signature == other.signature and | ||||||
|  |                 self.arguments == other.arguments) | ||||||
|  | 
 | ||||||
|  |     def __ne__(self, other): | ||||||
|  |         return not self.__eq__(other) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Signature(object): | ||||||
|  |     '''A Signature object represents the overall signature of a function. | ||||||
|  |     It stores a Parameter object for each parameter accepted by the | ||||||
|  |     function, as well as information specific to the function itself. | ||||||
|  |     A Signature object has the following public attributes and methods: | ||||||
|  |     * parameters : OrderedDict | ||||||
|  |         An ordered mapping of parameters' names to the corresponding | ||||||
|  |         Parameter objects (keyword-only arguments are in the same order | ||||||
|  |         as listed in `code.co_varnames`). | ||||||
|  |     * return_annotation : object | ||||||
|  |         The annotation for the return type of the function if specified. | ||||||
|  |         If the function has no annotation for its return type, this | ||||||
|  |         attribute is not set. | ||||||
|  |     * bind(*args, **kwargs) -> BoundArguments | ||||||
|  |         Creates a mapping from positional and keyword arguments to | ||||||
|  |         parameters. | ||||||
|  |     * bind_partial(*args, **kwargs) -> BoundArguments | ||||||
|  |         Creates a partial mapping from positional and keyword arguments | ||||||
|  |         to parameters (simulating 'functools.partial' behavior.) | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     __slots__ = ('_return_annotation', '_parameters') | ||||||
|  | 
 | ||||||
|  |     _parameter_cls = Parameter | ||||||
|  |     _bound_arguments_cls = BoundArguments | ||||||
|  | 
 | ||||||
|  |     empty = _empty | ||||||
|  | 
 | ||||||
|  |     def __init__(self, parameters=None, return_annotation=_empty, | ||||||
|  |                  __validate_parameters__=True): | ||||||
|  |         '''Constructs Signature from the given list of Parameter | ||||||
|  |         objects and 'return_annotation'.  All arguments are optional. | ||||||
|  |         ''' | ||||||
|  | 
 | ||||||
|  |         if parameters is None: | ||||||
|  |             params = OrderedDict() | ||||||
|  |         else: | ||||||
|  |             if __validate_parameters__: | ||||||
|  |                 params = OrderedDict() | ||||||
|  |                 top_kind = _POSITIONAL_ONLY | ||||||
|  | 
 | ||||||
|  |                 for idx, param in enumerate(parameters): | ||||||
|  |                     kind = param.kind | ||||||
|  |                     if kind < top_kind: | ||||||
|  |                         msg = 'wrong parameter order: {0} before {1}' | ||||||
|  |                         msg = msg.format(top_kind, param.kind) | ||||||
|  |                         raise ValueError(msg) | ||||||
|  |                     else: | ||||||
|  |                         top_kind = kind | ||||||
|  | 
 | ||||||
|  |                     name = param.name | ||||||
|  |                     if name is None: | ||||||
|  |                         name = str(idx) | ||||||
|  |                         param = param.replace(name=name) | ||||||
|  | 
 | ||||||
|  |                     if name in params: | ||||||
|  |                         msg = 'duplicate parameter name: {0!r}'.format(name) | ||||||
|  |                         raise ValueError(msg) | ||||||
|  |                     params[name] = param | ||||||
|  |             else: | ||||||
|  |                 params = OrderedDict(((param.name, param) | ||||||
|  |                                                 for param in parameters)) | ||||||
|  | 
 | ||||||
|  |         self._parameters = params | ||||||
|  |         self._return_annotation = return_annotation | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def from_function(cls, func): | ||||||
|  |         '''Constructs Signature for the given python function''' | ||||||
|  | 
 | ||||||
|  |         if not isinstance(func, types.FunctionType): | ||||||
|  |             raise TypeError('{0!r} is not a Python function'.format(func)) | ||||||
|  | 
 | ||||||
|  |         Parameter = cls._parameter_cls | ||||||
|  | 
 | ||||||
|  |         # Parameter information. | ||||||
|  |         func_code = func.__code__ | ||||||
|  |         pos_count = func_code.co_argcount | ||||||
|  |         arg_names = func_code.co_varnames | ||||||
|  |         positional = tuple(arg_names[:pos_count]) | ||||||
|  |         keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0) | ||||||
|  |         keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)] | ||||||
|  |         annotations = getattr(func, '__annotations__', {}) | ||||||
|  |         defaults = func.__defaults__ | ||||||
|  |         kwdefaults = getattr(func, '__kwdefaults__', None) | ||||||
|  | 
 | ||||||
|  |         if defaults: | ||||||
|  |             pos_default_count = len(defaults) | ||||||
|  |         else: | ||||||
|  |             pos_default_count = 0 | ||||||
|  | 
 | ||||||
|  |         parameters = [] | ||||||
|  | 
 | ||||||
|  |         # Non-keyword-only parameters w/o defaults. | ||||||
|  |         non_default_count = pos_count - pos_default_count | ||||||
|  |         for name in positional[:non_default_count]: | ||||||
|  |             annotation = annotations.get(name, _empty) | ||||||
|  |             parameters.append(Parameter(name, annotation=annotation, | ||||||
|  |                                         kind=_POSITIONAL_OR_KEYWORD)) | ||||||
|  | 
 | ||||||
|  |         # ... w/ defaults. | ||||||
|  |         for offset, name in enumerate(positional[non_default_count:]): | ||||||
|  |             annotation = annotations.get(name, _empty) | ||||||
|  |             parameters.append(Parameter(name, annotation=annotation, | ||||||
|  |                                         kind=_POSITIONAL_OR_KEYWORD, | ||||||
|  |                                         default=defaults[offset])) | ||||||
|  | 
 | ||||||
|  |         # *args | ||||||
|  |         if func_code.co_flags & 0x04: | ||||||
|  |             name = arg_names[pos_count + keyword_only_count] | ||||||
|  |             annotation = annotations.get(name, _empty) | ||||||
|  |             parameters.append(Parameter(name, annotation=annotation, | ||||||
|  |                                         kind=_VAR_POSITIONAL)) | ||||||
|  | 
 | ||||||
|  |         # Keyword-only parameters. | ||||||
|  |         for name in keyword_only: | ||||||
|  |             default = _empty | ||||||
|  |             if kwdefaults is not None: | ||||||
|  |                 default = kwdefaults.get(name, _empty) | ||||||
|  | 
 | ||||||
|  |             annotation = annotations.get(name, _empty) | ||||||
|  |             parameters.append(Parameter(name, annotation=annotation, | ||||||
|  |                                         kind=_KEYWORD_ONLY, | ||||||
|  |                                         default=default)) | ||||||
|  |         # **kwargs | ||||||
|  |         if func_code.co_flags & 0x08: | ||||||
|  |             index = pos_count + keyword_only_count | ||||||
|  |             if func_code.co_flags & 0x04: | ||||||
|  |                 index += 1 | ||||||
|  | 
 | ||||||
|  |             name = arg_names[index] | ||||||
|  |             annotation = annotations.get(name, _empty) | ||||||
|  |             parameters.append(Parameter(name, annotation=annotation, | ||||||
|  |                                         kind=_VAR_KEYWORD)) | ||||||
|  | 
 | ||||||
|  |         return cls(parameters, | ||||||
|  |                    return_annotation=annotations.get('return', _empty), | ||||||
|  |                    __validate_parameters__=False) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def parameters(self): | ||||||
|  |         try: | ||||||
|  |             return types.MappingProxyType(self._parameters) | ||||||
|  |         except AttributeError: | ||||||
|  |             return OrderedDict(self._parameters.items()) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def return_annotation(self): | ||||||
|  |         return self._return_annotation | ||||||
|  | 
 | ||||||
|  |     def replace(self, parameters=_void, return_annotation=_void): | ||||||
|  |         '''Creates a customized copy of the Signature. | ||||||
|  |         Pass 'parameters' and/or 'return_annotation' arguments | ||||||
|  |         to override them in the new copy. | ||||||
|  |         ''' | ||||||
|  | 
 | ||||||
|  |         if parameters is _void: | ||||||
|  |             parameters = self.parameters.values() | ||||||
|  | 
 | ||||||
|  |         if return_annotation is _void: | ||||||
|  |             return_annotation = self._return_annotation | ||||||
|  | 
 | ||||||
|  |         return type(self)(parameters, | ||||||
|  |                           return_annotation=return_annotation) | ||||||
|  | 
 | ||||||
|  |     def __hash__(self): | ||||||
|  |         msg = "unhashable type: '{0}'".format(self.__class__.__name__) | ||||||
|  |         raise TypeError(msg) | ||||||
|  | 
 | ||||||
|  |     def __eq__(self, other): | ||||||
|  |         if (not issubclass(type(other), Signature) or | ||||||
|  |                     self.return_annotation != other.return_annotation or | ||||||
|  |                     len(self.parameters) != len(other.parameters)): | ||||||
|  |             return False | ||||||
|  | 
 | ||||||
|  |         other_positions = dict((param, idx) | ||||||
|  |                            for idx, param in enumerate(other.parameters.keys())) | ||||||
|  | 
 | ||||||
|  |         for idx, (param_name, param) in enumerate(self.parameters.items()): | ||||||
|  |             if param.kind == _KEYWORD_ONLY: | ||||||
|  |                 try: | ||||||
|  |                     other_param = other.parameters[param_name] | ||||||
|  |                 except KeyError: | ||||||
|  |                     return False | ||||||
|  |                 else: | ||||||
|  |                     if param != other_param: | ||||||
|  |                         return False | ||||||
|  |             else: | ||||||
|  |                 try: | ||||||
|  |                     other_idx = other_positions[param_name] | ||||||
|  |                 except KeyError: | ||||||
|  |                     return False | ||||||
|  |                 else: | ||||||
|  |                     if (idx != other_idx or | ||||||
|  |                                     param != other.parameters[param_name]): | ||||||
|  |                         return False | ||||||
|  | 
 | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def __ne__(self, other): | ||||||
|  |         return not self.__eq__(other) | ||||||
|  | 
 | ||||||
|  |     def _bind(self, args, kwargs, partial=False): | ||||||
|  |         '''Private method.  Don't use directly.''' | ||||||
|  | 
 | ||||||
|  |         arguments = OrderedDict() | ||||||
|  | 
 | ||||||
|  |         parameters = iter(self.parameters.values()) | ||||||
|  |         parameters_ex = () | ||||||
|  |         arg_vals = iter(args) | ||||||
|  | 
 | ||||||
|  |         if partial: | ||||||
|  |             # Support for binding arguments to 'functools.partial' objects. | ||||||
|  |             # See 'functools.partial' case in 'signature()' implementation | ||||||
|  |             # for details. | ||||||
|  |             for param_name, param in self.parameters.items(): | ||||||
|  |                 if (param._partial_kwarg and param_name not in kwargs): | ||||||
|  |                     # Simulating 'functools.partial' behavior | ||||||
|  |                     kwargs[param_name] = param.default | ||||||
|  | 
 | ||||||
|  |         while True: | ||||||
|  |             # Let's iterate through the positional arguments and corresponding | ||||||
|  |             # parameters | ||||||
|  |             try: | ||||||
|  |                 arg_val = next(arg_vals) | ||||||
|  |             except StopIteration: | ||||||
|  |                 # No more positional arguments | ||||||
|  |                 try: | ||||||
|  |                     param = next(parameters) | ||||||
|  |                 except StopIteration: | ||||||
|  |                     # No more parameters. That's it. Just need to check that | ||||||
|  |                     # we have no `kwargs` after this while loop | ||||||
|  |                     break | ||||||
|  |                 else: | ||||||
|  |                     if param.kind == _VAR_POSITIONAL: | ||||||
|  |                         # That's OK, just empty *args.  Let's start parsing | ||||||
|  |                         # kwargs | ||||||
|  |                         break | ||||||
|  |                     elif param.name in kwargs: | ||||||
|  |                         if param.kind == _POSITIONAL_ONLY: | ||||||
|  |                             msg = '{arg!r} parameter is positional only, ' \ | ||||||
|  |                                   'but was passed as a keyword' | ||||||
|  |                             msg = msg.format(arg=param.name) | ||||||
|  |                             raise TypeError(msg) | ||||||
|  |                         parameters_ex = (param,) | ||||||
|  |                         break | ||||||
|  |                     elif (param.kind == _VAR_KEYWORD or | ||||||
|  |                                                 param.default is not _empty): | ||||||
|  |                         # That's fine too - we have a default value for this | ||||||
|  |                         # parameter.  So, lets start parsing `kwargs`, starting | ||||||
|  |                         # with the current parameter | ||||||
|  |                         parameters_ex = (param,) | ||||||
|  |                         break | ||||||
|  |                     else: | ||||||
|  |                         if partial: | ||||||
|  |                             parameters_ex = (param,) | ||||||
|  |                             break | ||||||
|  |                         else: | ||||||
|  |                             msg = '{arg!r} parameter lacking default value' | ||||||
|  |                             msg = msg.format(arg=param.name) | ||||||
|  |                             raise TypeError(msg) | ||||||
|  |             else: | ||||||
|  |                 # We have a positional argument to process | ||||||
|  |                 try: | ||||||
|  |                     param = next(parameters) | ||||||
|  |                 except StopIteration: | ||||||
|  |                     raise TypeError('too many positional arguments') | ||||||
|  |                 else: | ||||||
|  |                     if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): | ||||||
|  |                         # Looks like we have no parameter for this positional | ||||||
|  |                         # argument | ||||||
|  |                         raise TypeError('too many positional arguments') | ||||||
|  | 
 | ||||||
|  |                     if param.kind == _VAR_POSITIONAL: | ||||||
|  |                         # We have an '*args'-like argument, let's fill it with | ||||||
|  |                         # all positional arguments we have left and move on to | ||||||
|  |                         # the next phase | ||||||
|  |                         values = [arg_val] | ||||||
|  |                         values.extend(arg_vals) | ||||||
|  |                         arguments[param.name] = tuple(values) | ||||||
|  |                         break | ||||||
|  | 
 | ||||||
|  |                     if param.name in kwargs: | ||||||
|  |                         raise TypeError('multiple values for argument ' | ||||||
|  |                                         '{arg!r}'.format(arg=param.name)) | ||||||
|  | 
 | ||||||
|  |                     arguments[param.name] = arg_val | ||||||
|  | 
 | ||||||
|  |         # Now, we iterate through the remaining parameters to process | ||||||
|  |         # keyword arguments | ||||||
|  |         kwargs_param = None | ||||||
|  |         for param in itertools.chain(parameters_ex, parameters): | ||||||
|  |             if param.kind == _POSITIONAL_ONLY: | ||||||
|  |                 # This should never happen in case of a properly built | ||||||
|  |                 # Signature object (but let's have this check here | ||||||
|  |                 # to ensure correct behaviour just in case) | ||||||
|  |                 raise TypeError('{arg!r} parameter is positional only, ' | ||||||
|  |                                 'but was passed as a keyword'. \ | ||||||
|  |                                 format(arg=param.name)) | ||||||
|  | 
 | ||||||
|  |             if param.kind == _VAR_KEYWORD: | ||||||
|  |                 # Memorize that we have a '**kwargs'-like parameter | ||||||
|  |                 kwargs_param = param | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|  |             param_name = param.name | ||||||
|  |             try: | ||||||
|  |                 arg_val = kwargs.pop(param_name) | ||||||
|  |             except KeyError: | ||||||
|  |                 # We have no value for this parameter.  It's fine though, | ||||||
|  |                 # if it has a default value, or it is an '*args'-like | ||||||
|  |                 # parameter, left alone by the processing of positional | ||||||
|  |                 # arguments. | ||||||
|  |                 if (not partial and param.kind != _VAR_POSITIONAL and | ||||||
|  |                                                     param.default is _empty): | ||||||
|  |                     raise TypeError('{arg!r} parameter lacking default value'. \ | ||||||
|  |                                     format(arg=param_name)) | ||||||
|  | 
 | ||||||
|  |             else: | ||||||
|  |                 arguments[param_name] = arg_val | ||||||
|  | 
 | ||||||
|  |         if kwargs: | ||||||
|  |             if kwargs_param is not None: | ||||||
|  |                 # Process our '**kwargs'-like parameter | ||||||
|  |                 arguments[kwargs_param.name] = kwargs | ||||||
|  |             else: | ||||||
|  |                 raise TypeError('too many keyword arguments') | ||||||
|  | 
 | ||||||
|  |         return self._bound_arguments_cls(self, arguments) | ||||||
|  | 
 | ||||||
|  |     def bind(self, *args, **kwargs): | ||||||
|  |         '''Get a BoundArguments object, that maps the passed `args` | ||||||
|  |         and `kwargs` to the function's signature.  Raises `TypeError` | ||||||
|  |         if the passed arguments can not be bound. | ||||||
|  |         ''' | ||||||
|  |         return self._bind(args, kwargs) | ||||||
|  | 
 | ||||||
|  |     def bind_partial(self, *args, **kwargs): | ||||||
|  |         '''Get a BoundArguments object, that partially maps the | ||||||
|  |         passed `args` and `kwargs` to the function's signature. | ||||||
|  |         Raises `TypeError` if the passed arguments can not be bound. | ||||||
|  |         ''' | ||||||
|  |         return self._bind(args, kwargs, partial=True) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         result = [] | ||||||
|  |         render_kw_only_separator = True | ||||||
|  |         for idx, param in enumerate(self.parameters.values()): | ||||||
|  |             formatted = str(param) | ||||||
|  | 
 | ||||||
|  |             kind = param.kind | ||||||
|  |             if kind == _VAR_POSITIONAL: | ||||||
|  |                 # OK, we have an '*args'-like parameter, so we won't need | ||||||
|  |                 # a '*' to separate keyword-only arguments | ||||||
|  |                 render_kw_only_separator = False | ||||||
|  |             elif kind == _KEYWORD_ONLY and render_kw_only_separator: | ||||||
|  |                 # We have a keyword-only parameter to render and we haven't | ||||||
|  |                 # rendered an '*args'-like parameter before, so add a '*' | ||||||
|  |                 # separator to the parameters list ("foo(arg1, *, arg2)" case) | ||||||
|  |                 result.append('*') | ||||||
|  |                 # This condition should be only triggered once, so | ||||||
|  |                 # reset the flag | ||||||
|  |                 render_kw_only_separator = False | ||||||
|  | 
 | ||||||
|  |             result.append(formatted) | ||||||
|  | 
 | ||||||
|  |         rendered = '({0})'.format(', '.join(result)) | ||||||
|  | 
 | ||||||
|  |         if self.return_annotation is not _empty: | ||||||
|  |             anno = formatannotation(self.return_annotation) | ||||||
|  |             rendered += ' -> {0}'.format(anno) | ||||||
|  | 
 | ||||||
|  |         return rendered | ||||||
|  | @ -21,6 +21,7 @@ def test__is_dunder(): | ||||||
|     for name in non_dunder_names: |     for name in non_dunder_names: | ||||||
|         assert _is_dunder(name) is False |         assert _is_dunder(name) is False | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def test__is_sunder(): | def test__is_sunder(): | ||||||
|     sunder_names = [ |     sunder_names = [ | ||||||
|         '_i_', |         '_i_', | ||||||
|  |  | ||||||
|  | @ -2,18 +2,13 @@ import re | ||||||
| from collections import Iterable, OrderedDict | from collections import Iterable, OrderedDict | ||||||
| from functools import partial | from functools import partial | ||||||
| 
 | 
 | ||||||
| import six |  | ||||||
| 
 |  | ||||||
| from graphql_relay import connection_from_list | from graphql_relay import connection_from_list | ||||||
| from promise import Promise, is_thenable | from promise import Promise, is_thenable | ||||||
| 
 | 
 | ||||||
| from ..types import (AbstractType, Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, | from ..types import (Boolean, Enum, Int, Interface, List, NonNull, Scalar, | ||||||
|                      Union) |                      String, Union) | ||||||
| from ..types.field import Field | from ..types.field import Field | ||||||
| from ..types.objecttype import ObjectType, ObjectTypeMeta | from ..types.objecttype import ObjectType, ObjectTypeOptions | ||||||
| from ..types.options import Options |  | ||||||
| from ..utils.is_base_type import is_base_type |  | ||||||
| from ..utils.props import props |  | ||||||
| from .node import is_node | from .node import is_node | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -41,56 +36,50 @@ class PageInfo(ObjectType): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ConnectionMeta(ObjectTypeMeta): | class ConnectionOptions(ObjectTypeOptions): | ||||||
|  |     node = None | ||||||
| 
 | 
 | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         # Also ensure initialization is only performed for subclasses of Model |  | ||||||
|         # (excluding Model class itself). |  | ||||||
|         if not is_base_type(bases, ConnectionMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 | 
 | ||||||
|         options = Options( | class Connection(ObjectType): | ||||||
|             attrs.pop('Meta', None), |  | ||||||
|             name=name, |  | ||||||
|             description=None, |  | ||||||
|             node=None, |  | ||||||
|         ) |  | ||||||
|         options.interfaces = () |  | ||||||
|         options.local_fields = OrderedDict() |  | ||||||
| 
 | 
 | ||||||
|         assert options.node, 'You have to provide a node in {}.Meta'.format(cls.__name__) |     class Meta: | ||||||
|         assert issubclass(options.node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), ( |         abstract = True | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, node=None, name=None, **options): | ||||||
|  |         _meta = ConnectionOptions(cls) | ||||||
|  |         assert node, 'You have to provide a node in {}.Meta'.format(cls.__name__) | ||||||
|  |         assert issubclass(node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)), ( | ||||||
|             'Received incompatible node "{}" for Connection {}.' |             'Received incompatible node "{}" for Connection {}.' | ||||||
|         ).format(options.node, name) |         ).format(node, cls.__name__) | ||||||
| 
 | 
 | ||||||
|         base_name = re.sub('Connection$', '', options.name) or options.node._meta.name |         base_name = re.sub('Connection$', '', name or cls.__name__) or node._meta.name | ||||||
|         if not options.name: |         if not name: | ||||||
|             options.name = '{}Connection'.format(base_name) |             name = '{}Connection'.format(base_name) | ||||||
| 
 | 
 | ||||||
|         edge_class = attrs.pop('Edge', None) |         edge_class = getattr(cls, 'Edge', None) | ||||||
|  |         _node = node | ||||||
| 
 | 
 | ||||||
|         class EdgeBase(AbstractType): |         class EdgeBase(object): | ||||||
|             node = Field(options.node, description='The item at the end of the edge') |             node = Field(_node, description='The item at the end of the edge') | ||||||
|             cursor = String(required=True, description='A cursor for use in pagination') |             cursor = String(required=True, description='A cursor for use in pagination') | ||||||
| 
 | 
 | ||||||
|         edge_name = '{}Edge'.format(base_name) |         edge_name = '{}Edge'.format(base_name) | ||||||
|         if edge_class and issubclass(edge_class, AbstractType): |         if edge_class: | ||||||
|             edge = type(edge_name, (EdgeBase, edge_class, ObjectType, ), {}) |             edge_bases = (edge_class, EdgeBase, ObjectType,) | ||||||
|         else: |         else: | ||||||
|             edge_attrs = props(edge_class) if edge_class else {} |             edge_bases = (EdgeBase, ObjectType,) | ||||||
|             edge = type(edge_name, (EdgeBase, ObjectType, ), edge_attrs) |  | ||||||
| 
 | 
 | ||||||
|         class ConnectionBase(AbstractType): |         edge = type(edge_name, edge_bases, {}) | ||||||
|             page_info = Field(PageInfo, name='pageInfo', required=True) |         cls.Edge = edge | ||||||
|             edges = NonNull(List(edge)) |  | ||||||
| 
 | 
 | ||||||
|         bases = (ConnectionBase, ) + bases |         _meta.name = name | ||||||
|         attrs = dict(attrs, _meta=options, Edge=edge) |         _meta.node = node | ||||||
|         return ObjectTypeMeta.__new__(cls, name, bases, attrs) |         _meta.fields = OrderedDict([ | ||||||
| 
 |             ('page_info', Field(PageInfo, name='pageInfo', required=True)), | ||||||
| 
 |             ('edges', Field(NonNull(List(edge)))), | ||||||
| class Connection(six.with_metaclass(ConnectionMeta, ObjectType)): |         ]) | ||||||
|     pass |         return super(Connection, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class IterableConnectionField(Field): | class IterableConnectionField(Field): | ||||||
|  | @ -109,10 +98,13 @@ class IterableConnectionField(Field): | ||||||
|     @property |     @property | ||||||
|     def type(self): |     def type(self): | ||||||
|         type = super(IterableConnectionField, self).type |         type = super(IterableConnectionField, self).type | ||||||
|         if is_node(type): |  | ||||||
|             connection_type = type.Connection |  | ||||||
|         else: |  | ||||||
|         connection_type = type |         connection_type = type | ||||||
|  |         if is_node(type): | ||||||
|  |             raise Exception( | ||||||
|  |                 "ConnectionField's now need a explicit ConnectionType for Nodes.\n" | ||||||
|  |                 "Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#node-connections" | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|         assert issubclass(connection_type, Connection), ( |         assert issubclass(connection_type, Connection), ( | ||||||
|             '{} type have to be a subclass of Connection. Received "{}".' |             '{} type have to be a subclass of Connection. Received "{}".' | ||||||
|         ).format(self.__class__.__name__, connection_type) |         ).format(self.__class__.__name__, connection_type) | ||||||
|  | @ -138,8 +130,8 @@ class IterableConnectionField(Field): | ||||||
|         return connection |         return connection | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def connection_resolver(cls, resolver, connection_type, root, args, context, info): |     def connection_resolver(cls, resolver, connection_type, root, info, **args): | ||||||
|         resolved = resolver(root, args, context, info) |         resolved = resolver(root, info, **args) | ||||||
| 
 | 
 | ||||||
|         on_resolve = partial(cls.resolve_connection, connection_type, args) |         on_resolve = partial(cls.resolve_connection, connection_type, args) | ||||||
|         if is_thenable(resolved): |         if is_thenable(resolved): | ||||||
|  |  | ||||||
|  | @ -1,68 +1,72 @@ | ||||||
| import re | import re | ||||||
| from functools import partial | from collections import OrderedDict | ||||||
| 
 | 
 | ||||||
| import six | from promise import Promise, is_thenable | ||||||
| 
 | 
 | ||||||
| from promise import Promise | from ..types import Field, InputObjectType, String | ||||||
| 
 | from ..types.mutation import Mutation | ||||||
| from ..types import Field, AbstractType, Argument, InputObjectType, String |  | ||||||
| from ..types.mutation import Mutation, MutationMeta |  | ||||||
| from ..types.objecttype import ObjectTypeMeta |  | ||||||
| from ..utils.is_base_type import is_base_type |  | ||||||
| from ..utils.props import props |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ClientIDMutationMeta(MutationMeta): | class ClientIDMutation(Mutation): | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         # Also ensure initialization is only performed for subclasses of |  | ||||||
|         # Mutation |  | ||||||
|         if not is_base_type(bases, ClientIDMutationMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 | 
 | ||||||
|         input_class = attrs.pop('Input', None) |     class Meta: | ||||||
|         base_name = re.sub('Payload$', '', name) |         abstract = True | ||||||
|         if 'client_mutation_id' not in attrs: | 
 | ||||||
|             attrs['client_mutation_id'] = String(name='clientMutationId') |     @classmethod | ||||||
|         cls = ObjectTypeMeta.__new__(cls, '{}Payload'.format(base_name), bases, |     def __init_subclass_with_meta__(cls, output=None, input_fields=None, | ||||||
|                                      attrs) |                                     arguments=None, name=None, **options): | ||||||
|  |         input_class = getattr(cls, 'Input', None) | ||||||
|  |         base_name = re.sub('Payload$', '', name or cls.__name__) | ||||||
|  | 
 | ||||||
|  |         assert not output, "Can't specify any output" | ||||||
|  |         assert not arguments, "Can't specify any arguments" | ||||||
|  | 
 | ||||||
|  |         bases = (InputObjectType, ) | ||||||
|  |         if input_class: | ||||||
|  |             bases += (input_class, ) | ||||||
|  | 
 | ||||||
|  |         if not input_fields: | ||||||
|  |             input_fields = {} | ||||||
|  | 
 | ||||||
|  |         cls.Input = type( | ||||||
|  |             '{}Input'.format(base_name), | ||||||
|  |             bases, | ||||||
|  |             OrderedDict(input_fields, client_mutation_id=String( | ||||||
|  |                 name='clientMutationId')) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         arguments = OrderedDict( | ||||||
|  |             input=cls.Input(required=True) | ||||||
|  |             # 'client_mutation_id': String(name='clientMutationId') | ||||||
|  |         ) | ||||||
|         mutate_and_get_payload = getattr(cls, 'mutate_and_get_payload', None) |         mutate_and_get_payload = getattr(cls, 'mutate_and_get_payload', None) | ||||||
|         if cls.mutate and cls.mutate.__func__ == ClientIDMutation.mutate.__func__: |         if cls.mutate and cls.mutate.__func__ == ClientIDMutation.mutate.__func__: | ||||||
|             assert mutate_and_get_payload, ( |             assert mutate_and_get_payload, ( | ||||||
|                 "{}.mutate_and_get_payload method is required" |                 "{name}.mutate_and_get_payload method is required" | ||||||
|                 " in a ClientIDMutation.").format(name) |                 " in a ClientIDMutation.").format(name=name or cls.__name__) | ||||||
|         input_attrs = {} |  | ||||||
|         bases = () |  | ||||||
|         if not input_class: |  | ||||||
|             input_attrs = {} |  | ||||||
|         elif not issubclass(input_class, AbstractType): |  | ||||||
|             input_attrs = props(input_class) |  | ||||||
|         else: |  | ||||||
|             bases += (input_class, ) |  | ||||||
|         input_attrs['client_mutation_id'] = String(name='clientMutationId') |  | ||||||
|         cls.Input = type('{}Input'.format(base_name), |  | ||||||
|                          bases + (InputObjectType, ), input_attrs) |  | ||||||
|         output_class = getattr(cls, 'Output', cls) |  | ||||||
|         cls.Field = partial( |  | ||||||
|             Field, |  | ||||||
|             output_class, |  | ||||||
|             resolver=cls.mutate, |  | ||||||
|             input=Argument(cls.Input, required=True)) |  | ||||||
|         return cls |  | ||||||
| 
 | 
 | ||||||
|  |         if not name: | ||||||
|  |             name = '{}Payload'.format(base_name) | ||||||
|  | 
 | ||||||
|  |         super(ClientIDMutation, cls).__init_subclass_with_meta__( | ||||||
|  |             output=None, arguments=arguments, name=name, **options) | ||||||
|  |         cls._meta.fields['client_mutation_id'] = ( | ||||||
|  |             Field(String, name='clientMutationId') | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
| class ClientIDMutation(six.with_metaclass(ClientIDMutationMeta, Mutation)): |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def mutate(cls, root, args, context, info): |     def mutate(cls, root, info, input): | ||||||
|         input = args.get('input') |  | ||||||
| 
 |  | ||||||
|         def on_resolve(payload): |         def on_resolve(payload): | ||||||
|             try: |             try: | ||||||
|                 payload.client_mutation_id = input.get('clientMutationId') |                 payload.client_mutation_id = input.get('client_mutation_id') | ||||||
|             except: |             except: | ||||||
|                 raise Exception( |                 raise Exception( | ||||||
|                     ('Cannot set client_mutation_id in the payload object {}' |                     ('Cannot set client_mutation_id in the payload object {}' | ||||||
|                      ).format(repr(payload))) |                      ).format(repr(payload))) | ||||||
|             return payload |             return payload | ||||||
| 
 | 
 | ||||||
|         return Promise.resolve( |         result = cls.mutate_and_get_payload(root, info, **input) | ||||||
|             cls.mutate_and_get_payload(input, context, info)).then(on_resolve) |         if is_thenable(result): | ||||||
|  |             return Promise.resolve(result).then(on_resolve) | ||||||
|  | 
 | ||||||
|  |         return on_resolve(result) | ||||||
|  |  | ||||||
|  | @ -1,12 +1,11 @@ | ||||||
|  | from collections import OrderedDict | ||||||
| from functools import partial | from functools import partial | ||||||
| 
 | 
 | ||||||
| import six |  | ||||||
| 
 |  | ||||||
| from graphql_relay import from_global_id, to_global_id | from graphql_relay import from_global_id, to_global_id | ||||||
| 
 | 
 | ||||||
| from ..types import ID, Field, Interface, ObjectType | from ..types import ID, Field, Interface, ObjectType | ||||||
|  | from ..types.interface import InterfaceOptions | ||||||
| from ..types.utils import get_type | from ..types.utils import get_type | ||||||
| from ..types.interface import InterfaceMeta |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def is_node(objecttype): | def is_node(objecttype): | ||||||
|  | @ -22,18 +21,6 @@ def is_node(objecttype): | ||||||
|     return False |     return False | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def get_default_connection(cls): |  | ||||||
|     from .connection import Connection |  | ||||||
|     assert issubclass(cls, ObjectType), ( |  | ||||||
|         'Can only get connection type on implemented Nodes.' |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     class Meta: |  | ||||||
|         node = cls |  | ||||||
| 
 |  | ||||||
|     return type('{}Connection'.format(cls.__name__), (Connection,), {'Meta': Meta}) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class GlobalID(Field): | class GlobalID(Field): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs): |     def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs): | ||||||
|  | @ -42,21 +29,15 @@ class GlobalID(Field): | ||||||
|         self.parent_type_name = parent_type._meta.name if parent_type else None |         self.parent_type_name = parent_type._meta.name if parent_type else None | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def id_resolver(parent_resolver, node, root, args, context, info, parent_type_name=None): |     def id_resolver(parent_resolver, node, root, info, parent_type_name=None, **args): | ||||||
|         type_id = parent_resolver(root, args, context, info) |         type_id = parent_resolver(root, info, **args) | ||||||
|         parent_type_name = parent_type_name or info.parent_type.name |         parent_type_name = parent_type_name or info.parent_type.name | ||||||
|         return node.to_global_id(parent_type_name, type_id)  # root._meta.name |         return node.to_global_id(parent_type_name, type_id)  # root._meta.name | ||||||
| 
 | 
 | ||||||
|     def get_resolver(self, parent_resolver): |     def get_resolver(self, parent_resolver): | ||||||
|         return partial(self.id_resolver, parent_resolver, self.node, parent_type_name=self.parent_type_name) |         return partial( | ||||||
| 
 |             self.id_resolver, parent_resolver, self.node, parent_type_name=self.parent_type_name | ||||||
| 
 |         ) | ||||||
| class NodeMeta(InterfaceMeta): |  | ||||||
| 
 |  | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         cls = InterfaceMeta.__new__(cls, name, bases, attrs) |  | ||||||
|         cls._meta.fields['id'] = GlobalID(cls, description='The ID of the object.') |  | ||||||
|         return cls |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NodeField(Field): | class NodeField(Field): | ||||||
|  | @ -75,10 +56,24 @@ class NodeField(Field): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def get_resolver(self, parent_resolver): |     def get_resolver(self, parent_resolver): | ||||||
|         return partial(self.node_type.node_resolver, only_type=get_type(self.field_type)) |         return partial(self.node_type.node_resolver, get_type(self.field_type)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Node(six.with_metaclass(NodeMeta, Interface)): | class AbstractNode(Interface): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         abstract = True | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, **options): | ||||||
|  |         _meta = InterfaceOptions(cls) | ||||||
|  |         _meta.fields = OrderedDict( | ||||||
|  |             id=GlobalID(cls, description='The ID of the object.') | ||||||
|  |         ) | ||||||
|  |         super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Node(AbstractNode): | ||||||
|     '''An object with an ID''' |     '''An object with an ID''' | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|  | @ -86,11 +81,11 @@ class Node(six.with_metaclass(NodeMeta, Interface)): | ||||||
|         return NodeField(cls, *args, **kwargs) |         return NodeField(cls, *args, **kwargs) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def node_resolver(cls, root, args, context, info, only_type=None): |     def node_resolver(cls, only_type, root, info, id): | ||||||
|         return cls.get_node_from_global_id(args.get('id'), context, info, only_type) |         return cls.get_node_from_global_id(info, id, only_type=only_type) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_node_from_global_id(cls, global_id, context, info, only_type=None): |     def get_node_from_global_id(cls, info, global_id, only_type=None): | ||||||
|         try: |         try: | ||||||
|             _type, _id = cls.from_global_id(global_id) |             _type, _id = cls.from_global_id(global_id) | ||||||
|             graphene_type = info.schema.get_type(_type).graphene_type |             graphene_type = info.schema.get_type(_type).graphene_type | ||||||
|  | @ -108,7 +103,7 @@ class Node(six.with_metaclass(NodeMeta, Interface)): | ||||||
| 
 | 
 | ||||||
|         get_node = getattr(graphene_type, 'get_node', None) |         get_node = getattr(graphene_type, 'get_node', None) | ||||||
|         if get_node: |         if get_node: | ||||||
|             return get_node(_id, context, info) |             return get_node(info, _id) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def from_global_id(cls, global_id): |     def from_global_id(cls, global_id): | ||||||
|  | @ -117,11 +112,3 @@ class Node(six.with_metaclass(NodeMeta, Interface)): | ||||||
|     @classmethod |     @classmethod | ||||||
|     def to_global_id(cls, type, id): |     def to_global_id(cls, type, id): | ||||||
|         return to_global_id(type, id) |         return to_global_id(type, id) | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def implements(cls, objecttype): |  | ||||||
|         get_connection = getattr(objecttype, 'get_connection', None) |  | ||||||
|         if not get_connection: |  | ||||||
|             get_connection = partial(get_default_connection, objecttype) |  | ||||||
| 
 |  | ||||||
|         objecttype.Connection = get_connection() |  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
|  | import pytest | ||||||
| 
 | 
 | ||||||
| from ...types import AbstractType, Field, List, NonNull, ObjectType, String, Argument, Int | from ...types import Argument, Field, Int, List, NonNull, ObjectType, String | ||||||
| from ..connection import Connection, PageInfo, ConnectionField | from ..connection import Connection, ConnectionField, PageInfo | ||||||
| from ..node import Node | from ..node import Node | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -38,7 +39,7 @@ def test_connection(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_connection_inherit_abstracttype(): | def test_connection_inherit_abstracttype(): | ||||||
|     class BaseConnection(AbstractType): |     class BaseConnection(object): | ||||||
|         extra = String() |         extra = String() | ||||||
| 
 | 
 | ||||||
|     class MyObjectConnection(BaseConnection, Connection): |     class MyObjectConnection(BaseConnection, Connection): | ||||||
|  | @ -73,7 +74,7 @@ def test_edge(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_edge_with_bases(): | def test_edge_with_bases(): | ||||||
|     class BaseEdge(AbstractType): |     class BaseEdge(object): | ||||||
|         extra = String() |         extra = String() | ||||||
| 
 | 
 | ||||||
|     class MyObjectConnection(Connection): |     class MyObjectConnection(Connection): | ||||||
|  | @ -96,16 +97,6 @@ def test_edge_with_bases(): | ||||||
|     assert edge_fields['other'].type == String |     assert edge_fields['other'].type == String | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_edge_on_node(): |  | ||||||
|     Edge = MyObject.Connection.Edge |  | ||||||
|     assert Edge._meta.name == 'MyObjectEdge' |  | ||||||
|     edge_fields = Edge._meta.fields |  | ||||||
|     assert list(edge_fields.keys()) == ['node', 'cursor'] |  | ||||||
| 
 |  | ||||||
|     assert isinstance(edge_fields['node'], Field) |  | ||||||
|     assert edge_fields['node'].type == MyObject |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_pageinfo(): | def test_pageinfo(): | ||||||
|     assert PageInfo._meta.name == 'PageInfo' |     assert PageInfo._meta.name == 'PageInfo' | ||||||
|     fields = PageInfo._meta.fields |     fields = PageInfo._meta.fields | ||||||
|  | @ -114,6 +105,7 @@ def test_pageinfo(): | ||||||
| 
 | 
 | ||||||
| def test_connectionfield(): | def test_connectionfield(): | ||||||
|     class MyObjectConnection(Connection): |     class MyObjectConnection(Connection): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             node = MyObject |             node = MyObject | ||||||
| 
 | 
 | ||||||
|  | @ -126,8 +118,16 @@ def test_connectionfield(): | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_connectionfield_node_deprecated(): | ||||||
|  |     field = ConnectionField(MyObject) | ||||||
|  |     with pytest.raises(Exception) as exc_info: | ||||||
|  |         field.type | ||||||
|  |      | ||||||
|  |     assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str(exc_info.value) | ||||||
|  | 
 | ||||||
| def test_connectionfield_custom_args(): | def test_connectionfield_custom_args(): | ||||||
|     class MyObjectConnection(Connection): |     class MyObjectConnection(Connection): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             node = MyObject |             node = MyObject | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ from graphql_relay.utils import base64 | ||||||
| from promise import Promise | from promise import Promise | ||||||
| 
 | 
 | ||||||
| from ...types import ObjectType, Schema, String | from ...types import ObjectType, Schema, String | ||||||
| from ..connection import ConnectionField, PageInfo | from ..connection import Connection, ConnectionField, PageInfo | ||||||
| from ..node import Node | from ..node import Node | ||||||
| 
 | 
 | ||||||
| letter_chars = ['A', 'B', 'C', 'D', 'E'] | letter_chars = ['A', 'B', 'C', 'D', 'E'] | ||||||
|  | @ -18,27 +18,33 @@ class Letter(ObjectType): | ||||||
|     letter = String() |     letter = String() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class LetterConnection(Connection): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         node = Letter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class Query(ObjectType): | class Query(ObjectType): | ||||||
|     letters = ConnectionField(Letter) |     letters = ConnectionField(LetterConnection) | ||||||
|     connection_letters = ConnectionField(Letter) |     connection_letters = ConnectionField(LetterConnection) | ||||||
|     promise_letters = ConnectionField(Letter) |     promise_letters = ConnectionField(LetterConnection) | ||||||
| 
 | 
 | ||||||
|     node = Node.Field() |     node = Node.Field() | ||||||
| 
 | 
 | ||||||
|     def resolve_letters(self, args, context, info): |     def resolve_letters(self, info, **args): | ||||||
|         return list(letters.values()) |         return list(letters.values()) | ||||||
| 
 | 
 | ||||||
|     def resolve_promise_letters(self, args, context, info): |     def resolve_promise_letters(self, info, **args): | ||||||
|         return Promise.resolve(list(letters.values())) |         return Promise.resolve(list(letters.values())) | ||||||
| 
 | 
 | ||||||
|     def resolve_connection_letters(self, args, context, info): |     def resolve_connection_letters(self, info, **args): | ||||||
|         return Letter.Connection( |         return LetterConnection( | ||||||
|             page_info=PageInfo( |             page_info=PageInfo( | ||||||
|                 has_next_page=True, |                 has_next_page=True, | ||||||
|                 has_previous_page=False |                 has_previous_page=False | ||||||
|             ), |             ), | ||||||
|             edges=[ |             edges=[ | ||||||
|                 Letter.Connection.Edge( |                 LetterConnection.Edge( | ||||||
|                     node=Letter(id=0, letter='A'), |                     node=Letter(id=0, letter='A'), | ||||||
|                     cursor='a-cursor' |                     cursor='a-cursor' | ||||||
|                 ), |                 ), | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| from graphql_relay import to_global_id | from graphql_relay import to_global_id | ||||||
| 
 | 
 | ||||||
| from ..node import Node, GlobalID | from ...types import ID, NonNull, ObjectType, String | ||||||
| from ...types import NonNull, ID, ObjectType, String |  | ||||||
| from ...types.definitions import GrapheneObjectType | from ...types.definitions import GrapheneObjectType | ||||||
|  | from ..node import GlobalID, Node | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CustomNode(Node): | class CustomNode(Node): | ||||||
|  | @ -48,7 +48,7 @@ def test_global_id_defaults_to_info_parent_type(): | ||||||
|     my_id = '1' |     my_id = '1' | ||||||
|     gid = GlobalID() |     gid = GlobalID() | ||||||
|     id_resolver = gid.get_resolver(lambda *_: my_id) |     id_resolver = gid.get_resolver(lambda *_: my_id) | ||||||
|     my_global_id = id_resolver(None, None, None, Info(User)) |     my_global_id = id_resolver(None, Info(User)) | ||||||
|     assert my_global_id == to_global_id(User._meta.name, my_id) |     assert my_global_id == to_global_id(User._meta.name, my_id) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -56,5 +56,5 @@ def test_global_id_allows_setting_customer_parent_type(): | ||||||
|     my_id = '1' |     my_id = '1' | ||||||
|     gid = GlobalID(parent_type=User) |     gid = GlobalID(parent_type=User) | ||||||
|     id_resolver = gid.get_resolver(lambda *_: my_id) |     id_resolver = gid.get_resolver(lambda *_: my_id) | ||||||
|     my_global_id = id_resolver(None, None, None, None) |     my_global_id = id_resolver(None, None) | ||||||
|     assert my_global_id == to_global_id(User._meta.name, my_id) |     assert my_global_id == to_global_id(User._meta.name, my_id) | ||||||
|  |  | ||||||
|  | @ -1,62 +1,86 @@ | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
| from ...types import (AbstractType, Argument, Field, InputField, |  | ||||||
|                       InputObjectType, NonNull, ObjectType, Schema) |  | ||||||
| from ...types.scalars import String |  | ||||||
| from ..mutation import ClientIDMutation |  | ||||||
| from ..node import Node |  | ||||||
| from promise import Promise | from promise import Promise | ||||||
| 
 | 
 | ||||||
|  | from ...types import (ID, Argument, Field, InputField, InputObjectType, | ||||||
|  |                       NonNull, ObjectType, Schema) | ||||||
|  | from ...types.scalars import String | ||||||
|  | from ..mutation import ClientIDMutation | ||||||
| 
 | 
 | ||||||
| class SharedFields(AbstractType): | 
 | ||||||
|  | class SharedFields(object): | ||||||
|     shared = String() |     shared = String() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MyNode(ObjectType): | class MyNode(ObjectType): | ||||||
|     class Meta: |     # class Meta: | ||||||
|         interfaces = (Node, ) |     #     interfaces = (Node, ) | ||||||
| 
 |     id = ID() | ||||||
|     name = String() |     name = String() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SaySomething(ClientIDMutation): | class SaySomething(ClientIDMutation): | ||||||
|  | 
 | ||||||
|     class Input: |     class Input: | ||||||
|         what = String() |         what = String() | ||||||
| 
 | 
 | ||||||
|     phrase = String() |     phrase = String() | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def mutate_and_get_payload(args, context, info): |     def mutate_and_get_payload(self, info, what, client_mutation_id=None): | ||||||
|         what = args.get('what') |  | ||||||
|         return SaySomething(phrase=str(what)) |         return SaySomething(phrase=str(what)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SaySomethingPromise(ClientIDMutation): | class FixedSaySomething(object): | ||||||
|  |     __slots__ = 'phrase', | ||||||
|  | 
 | ||||||
|  |     def __init__(self, phrase): | ||||||
|  |         self.phrase = phrase | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SaySomethingFixed(ClientIDMutation): | ||||||
|  | 
 | ||||||
|     class Input: |     class Input: | ||||||
|         what = String() |         what = String() | ||||||
| 
 | 
 | ||||||
|     phrase = String() |     phrase = String() | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def mutate_and_get_payload(args, context, info): |     def mutate_and_get_payload(self, info, what, client_mutation_id=None): | ||||||
|         what = args.get('what') |         return FixedSaySomething(phrase=str(what)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SaySomethingPromise(ClientIDMutation): | ||||||
|  | 
 | ||||||
|  |     class Input: | ||||||
|  |         what = String() | ||||||
|  | 
 | ||||||
|  |     phrase = String() | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def mutate_and_get_payload(self, info, what, client_mutation_id=None): | ||||||
|         return Promise.resolve(SaySomething(phrase=str(what))) |         return Promise.resolve(SaySomething(phrase=str(what))) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | # MyEdge = MyNode.Connection.Edge | ||||||
|  | class MyEdge(ObjectType): | ||||||
|  |     node = Field(MyNode) | ||||||
|  |     cursor = String() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class OtherMutation(ClientIDMutation): | class OtherMutation(ClientIDMutation): | ||||||
|  | 
 | ||||||
|     class Input(SharedFields): |     class Input(SharedFields): | ||||||
|         additional_field = String() |         additional_field = String() | ||||||
| 
 | 
 | ||||||
|     name = String() |     name = String() | ||||||
|     my_node_edge = Field(MyNode.Connection.Edge) |     my_node_edge = Field(MyEdge) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @staticmethod | ||||||
|     def mutate_and_get_payload(cls, args, context, info): |     def mutate_and_get_payload(self, info, shared='', additional_field='', client_mutation_id=None): | ||||||
|         shared = args.get('shared', '') |         edge_type = MyEdge | ||||||
|         additionalField = args.get('additionalField', '') |  | ||||||
|         edge_type = MyNode.Connection.Edge |  | ||||||
|         return OtherMutation( |         return OtherMutation( | ||||||
|             name=shared + additionalField, |             name=shared + additional_field, | ||||||
|             my_node_edge=edge_type(cursor='1', node=MyNode(name='name'))) |             my_node_edge=edge_type(cursor='1', node=MyNode(name='name'))) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -66,6 +90,7 @@ class RootQuery(ObjectType): | ||||||
| 
 | 
 | ||||||
| class Mutation(ObjectType): | class Mutation(ObjectType): | ||||||
|     say = SaySomething.Field() |     say = SaySomething.Field() | ||||||
|  |     say_fixed = SaySomethingFixed.Field() | ||||||
|     say_promise = SaySomethingPromise.Field() |     say_promise = SaySomethingPromise.Field() | ||||||
|     other = OtherMutation.Field() |     other = OtherMutation.Field() | ||||||
| 
 | 
 | ||||||
|  | @ -86,6 +111,7 @@ def test_no_mutate_and_get_payload(): | ||||||
| def test_mutation(): | def test_mutation(): | ||||||
|     fields = SaySomething._meta.fields |     fields = SaySomething._meta.fields | ||||||
|     assert list(fields.keys()) == ['phrase', 'client_mutation_id'] |     assert list(fields.keys()) == ['phrase', 'client_mutation_id'] | ||||||
|  |     assert SaySomething._meta.name == "SaySomethingPayload" | ||||||
|     assert isinstance(fields['phrase'], Field) |     assert isinstance(fields['phrase'], Field) | ||||||
|     field = SaySomething.Field() |     field = SaySomething.Field() | ||||||
|     assert field.type == SaySomething |     assert field.type == SaySomething | ||||||
|  | @ -146,7 +172,14 @@ def test_node_query(): | ||||||
|     assert executed.data == {'say': {'phrase': 'hello'}} |     assert executed.data == {'say': {'phrase': 'hello'}} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_node_query(): | def test_node_query_fixed(): | ||||||
|  |     executed = schema.execute( | ||||||
|  |         'mutation a { sayFixed(input: {what:"hello", clientMutationId:"1"}) { phrase } }' | ||||||
|  |     ) | ||||||
|  |     assert "Cannot set client_mutation_id in the payload object" in str(executed.errors[0]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_node_query_promise(): | ||||||
|     executed = schema.execute( |     executed = schema.execute( | ||||||
|         'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }' |         'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }' | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -2,12 +2,11 @@ from collections import OrderedDict | ||||||
| 
 | 
 | ||||||
| from graphql_relay import to_global_id | from graphql_relay import to_global_id | ||||||
| 
 | 
 | ||||||
| from ...types import AbstractType, ObjectType, Schema, String | from ...types import ObjectType, Schema, String | ||||||
| from ..connection import Connection | from ..node import Node, is_node | ||||||
| from ..node import Node |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SharedNodeFields(AbstractType): | class SharedNodeFields(object): | ||||||
| 
 | 
 | ||||||
|     shared = String() |     shared = String() | ||||||
|     something_else = String() |     something_else = String() | ||||||
|  | @ -23,7 +22,7 @@ class MyNode(ObjectType): | ||||||
|     name = String() |     name = String() | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_node(id, *_): |     def get_node(info, id): | ||||||
|         return MyNode(name=str(id)) |         return MyNode(name=str(id)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -37,7 +36,7 @@ class MyOtherNode(SharedNodeFields, ObjectType): | ||||||
|         return 'extra field info.' |         return 'extra field info.' | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_node(id, *_): |     def get_node(info, id): | ||||||
|         return MyOtherNode(shared=str(id)) |         return MyOtherNode(shared=str(id)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -47,20 +46,14 @@ class RootQuery(ObjectType): | ||||||
|     only_node = Node.Field(MyNode) |     only_node = Node.Field(MyNode) | ||||||
|     only_node_lazy = Node.Field(lambda: MyNode) |     only_node_lazy = Node.Field(lambda: MyNode) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode]) | schema = Schema(query=RootQuery, types=[MyNode, MyOtherNode]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_node_good(): | def test_node_good(): | ||||||
|     assert 'id' in MyNode._meta.fields |     assert 'id' in MyNode._meta.fields | ||||||
| 
 |     assert is_node(MyNode) | ||||||
| 
 |     assert not is_node(object) | ||||||
| def test_node_get_connection(): |  | ||||||
|     connection = MyNode.Connection |  | ||||||
|     assert issubclass(connection, Connection) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_node_get_connection_dont_duplicate(): |  | ||||||
|     assert MyNode.Connection == MyNode.Connection |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_node_query(): | def test_node_query(): | ||||||
|  | @ -80,6 +73,15 @@ def test_subclassed_node_query(): | ||||||
|         [('shared', '1'), ('extraField', 'extra field info.'), ('somethingElse', '----')])}) |         [('shared', '1'), ('extraField', 'extra field info.'), ('somethingElse', '----')])}) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_node_requesting_non_node(): | ||||||
|  |     executed = schema.execute( | ||||||
|  |         '{ node(id:"%s") { __typename } } ' % Node.to_global_id("RootQuery", 1) | ||||||
|  |     ) | ||||||
|  |     assert executed.data == { | ||||||
|  |         'node': None | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_node_query_incorrect_id(): | def test_node_query_incorrect_id(): | ||||||
|     executed = schema.execute( |     executed = schema.execute( | ||||||
|         '{ node(id:"%s") { ... on MyNode { name } } }' % "something:2" |         '{ node(id:"%s") { ... on MyNode { name } } }' % "something:2" | ||||||
|  | @ -114,7 +116,7 @@ def test_node_field_only_type_wrong(): | ||||||
|     ) |     ) | ||||||
|     assert len(executed.errors) == 1 |     assert len(executed.errors) == 1 | ||||||
|     assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.' |     assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.' | ||||||
|     assert executed.data == { 'onlyNode': None } |     assert executed.data == {'onlyNode': None} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_node_field_only_lazy_type(): | def test_node_field_only_lazy_type(): | ||||||
|  | @ -131,7 +133,7 @@ def test_node_field_only_lazy_type_wrong(): | ||||||
|     ) |     ) | ||||||
|     assert len(executed.errors) == 1 |     assert len(executed.errors) == 1 | ||||||
|     assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.' |     assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.' | ||||||
|     assert executed.data == { 'onlyNodeLazy': None } |     assert executed.data == {'onlyNodeLazy': None} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_str_schema(): | def test_str_schema(): | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ class CustomNode(Node): | ||||||
|         return id |         return id | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_node_from_global_id(id, context, info, only_type=None): |     def get_node_from_global_id(info, id, only_type=None): | ||||||
|         assert info.schema == schema |         assert info.schema == schema | ||||||
|         if id in user_data: |         if id in user_data: | ||||||
|             return user_data.get(id) |             return user_data.get(id) | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ def format_execution_result(execution_result, format_error): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Client(object): | class Client(object): | ||||||
|  | 
 | ||||||
|     def __init__(self, schema, format_error=None, **execute_options): |     def __init__(self, schema, format_error=None, **execute_options): | ||||||
|         assert isinstance(schema, Schema) |         assert isinstance(schema, Schema) | ||||||
|         self.schema = schema |         self.schema = schema | ||||||
|  |  | ||||||
|  | @ -3,9 +3,11 @@ | ||||||
| import graphene | import graphene | ||||||
| from graphene import resolve_only_args | from graphene import resolve_only_args | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Query(graphene.ObjectType): | class Query(graphene.ObjectType): | ||||||
|     rand = graphene.String() |     rand = graphene.String() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Success(graphene.ObjectType): | class Success(graphene.ObjectType): | ||||||
|     yeah = graphene.String() |     yeah = graphene.String() | ||||||
| 
 | 
 | ||||||
|  | @ -15,18 +17,19 @@ class Error(graphene.ObjectType): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CreatePostResult(graphene.Union): | class CreatePostResult(graphene.Union): | ||||||
|  | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         types = [Success, Error] |         types = [Success, Error] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CreatePost(graphene.Mutation): | class CreatePost(graphene.Mutation): | ||||||
|  | 
 | ||||||
|     class Input: |     class Input: | ||||||
|         text = graphene.String(required=True) |         text = graphene.String(required=True) | ||||||
| 
 | 
 | ||||||
|     result = graphene.Field(CreatePostResult) |     result = graphene.Field(CreatePostResult) | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     def mutate(self, info, text): | ||||||
|     def mutate(self, text): |  | ||||||
|         result = Success(yeah='yeah') |         result = Success(yeah='yeah') | ||||||
| 
 | 
 | ||||||
|         return CreatePost(result=result) |         return CreatePost(result=result) | ||||||
|  | @ -37,6 +40,7 @@ class Mutations(graphene.ObjectType): | ||||||
| 
 | 
 | ||||||
| # tests.py | # tests.py | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def test_create_post(): | def test_create_post(): | ||||||
|     query_string = ''' |     query_string = ''' | ||||||
|     mutation { |     mutation { | ||||||
|  |  | ||||||
|  | @ -1,19 +1,25 @@ | ||||||
| # https://github.com/graphql-python/graphene/issues/356 | # https://github.com/graphql-python/graphene/issues/356 | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
|  | 
 | ||||||
| import graphene | import graphene | ||||||
| from graphene import relay | from graphene import relay | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class SomeTypeOne(graphene.ObjectType): | class SomeTypeOne(graphene.ObjectType): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class SomeTypeTwo(graphene.ObjectType): | class SomeTypeTwo(graphene.ObjectType): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class MyUnion(graphene.Union): | class MyUnion(graphene.Union): | ||||||
|  | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         types = (SomeTypeOne, SomeTypeTwo) |         types = (SomeTypeOne, SomeTypeTwo) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def test_issue(): | def test_issue(): | ||||||
|     with pytest.raises(Exception) as exc_info: |     with pytest.raises(Exception) as exc_info: | ||||||
|         class Query(graphene.ObjectType): |         class Query(graphene.ObjectType): | ||||||
|  |  | ||||||
|  | @ -1,36 +1,25 @@ | ||||||
| # https://github.com/graphql-python/graphene/issues/425 | # https://github.com/graphql-python/graphene/issues/425 | ||||||
| import six | # Adapted for Graphene 2.0 | ||||||
| 
 | 
 | ||||||
| from graphene.utils.is_base_type import is_base_type | from graphene.types.objecttype import ObjectType, ObjectTypeOptions | ||||||
| 
 |  | ||||||
| from graphene.types.objecttype import ObjectTypeMeta, ObjectType |  | ||||||
| from graphene.types.options import Options |  | ||||||
| 
 |  | ||||||
| class SpecialObjectTypeMeta(ObjectTypeMeta): |  | ||||||
| 
 |  | ||||||
|     @staticmethod |  | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         # Also ensure initialization is only performed for subclasses of |  | ||||||
|         # DjangoObjectType |  | ||||||
|         if not is_base_type(bases, SpecialObjectTypeMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         options = Options( |  | ||||||
|             attrs.pop('Meta', None), |  | ||||||
|             other_attr='default', |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options)) |  | ||||||
|         assert cls._meta is options |  | ||||||
|         return cls |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SpecialObjectType(six.with_metaclass(SpecialObjectTypeMeta, ObjectType)): | class SpecialOptions(ObjectTypeOptions): | ||||||
|     pass |     other_attr = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SpecialObjectType(ObjectType): | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, other_attr='default', **options): | ||||||
|  |         _meta = SpecialOptions(cls) | ||||||
|  |         _meta.other_attr = other_attr | ||||||
|  |         super(SpecialObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_special_objecttype_could_be_subclassed(): | def test_special_objecttype_could_be_subclassed(): | ||||||
|     class MyType(SpecialObjectType): |     class MyType(SpecialObjectType): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             other_attr = 'yeah!' |             other_attr = 'yeah!' | ||||||
| 
 | 
 | ||||||
|  | @ -49,5 +38,5 @@ def test_special_objecttype_inherit_meta_options(): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|     assert MyType._meta.name == 'MyType' |     assert MyType._meta.name == 'MyType' | ||||||
|     assert MyType._meta.default_resolver == None |     assert MyType._meta.default_resolver is None | ||||||
|     assert MyType._meta.interfaces == () |     assert MyType._meta.interfaces == () | ||||||
|  |  | ||||||
|  | @ -1,14 +1,13 @@ | ||||||
| # https://github.com/graphql-python/graphene/issues/313 | # https://github.com/graphql-python/graphene/issues/313 | ||||||
| 
 | 
 | ||||||
| import graphene | import graphene | ||||||
| from graphene import resolve_only_args |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Query(graphene.ObjectType): | class Query(graphene.ObjectType): | ||||||
|     some_field = graphene.String(from_=graphene.String(name="from")) |     some_field = graphene.String(from_=graphene.String(name="from")) | ||||||
| 
 | 
 | ||||||
|     def resolve_some_field(_, args, context, infos): |     def resolve_some_field(self, info, from_=None): | ||||||
|         return args.get("from_") |         return from_ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_issue(): | def test_issue(): | ||||||
|  |  | ||||||
|  | @ -1,10 +1,12 @@ | ||||||
| # flake8: noqa | # flake8: noqa | ||||||
|  | from graphql import ResolveInfo | ||||||
| 
 | 
 | ||||||
| from .objecttype import ObjectType | from .objecttype import ObjectType | ||||||
| from .abstracttype import AbstractType |  | ||||||
| from .interface import Interface | from .interface import Interface | ||||||
| from .mutation import Mutation | from .mutation import Mutation | ||||||
| from .scalars import Scalar, String, ID, Int, Float, Boolean | from .scalars import Scalar, String, ID, Int, Float, Boolean | ||||||
|  | from .json import JSONString | ||||||
|  | from .uuid import UUID | ||||||
| from .schema import Schema | from .schema import Schema | ||||||
| from .structures import List, NonNull | from .structures import List, NonNull | ||||||
| from .enum import Enum | from .enum import Enum | ||||||
|  | @ -14,10 +16,13 @@ from .argument import Argument | ||||||
| from .inputobjecttype import InputObjectType | from .inputobjecttype import InputObjectType | ||||||
| from .dynamic import Dynamic | from .dynamic import Dynamic | ||||||
| from .union import Union | from .union import Union | ||||||
|  | from .context import Context | ||||||
|  | 
 | ||||||
|  | # Deprecated | ||||||
|  | from .abstracttype import AbstractType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| __all__ = [ | __all__ = [ | ||||||
|     'AbstractType', |  | ||||||
|     'ObjectType', |     'ObjectType', | ||||||
|     'InputObjectType', |     'InputObjectType', | ||||||
|     'Interface', |     'Interface', | ||||||
|  | @ -31,10 +36,17 @@ __all__ = [ | ||||||
|     'ID', |     'ID', | ||||||
|     'Int', |     'Int', | ||||||
|     'Float', |     'Float', | ||||||
|  |     'JSONString', | ||||||
|  |     'UUID', | ||||||
|     'Boolean', |     'Boolean', | ||||||
|     'List', |     'List', | ||||||
|     'NonNull', |     'NonNull', | ||||||
|     'Argument', |     'Argument', | ||||||
|     'Dynamic', |     'Dynamic', | ||||||
|     'Union', |     'Union', | ||||||
|  |     'Context', | ||||||
|  |     'ResolveInfo', | ||||||
|  | 
 | ||||||
|  |     # Deprecated | ||||||
|  |     'AbstractType', | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -1,41 +1,12 @@ | ||||||
| import six | from ..utils.subclass_with_meta import SubclassWithMeta | ||||||
| 
 | from ..utils.deprecated import warn_deprecation | ||||||
| from ..utils.is_base_type import is_base_type |  | ||||||
| from .options import Options |  | ||||||
| from .utils import get_base_fields, merge, yank_fields_from_attrs |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AbstractTypeMeta(type): | class AbstractType(SubclassWithMeta): | ||||||
|     ''' |  | ||||||
|     AbstractType Definition |  | ||||||
| 
 | 
 | ||||||
|     When we want to share fields across multiple types, like a Interface, |     def __init_subclass__(cls, *args, **kwargs): | ||||||
|     a ObjectType and a Input ObjectType we can use AbstractTypes for defining |         warn_deprecation( | ||||||
|     our fields that the other types will inherit from. |             "Abstract type is deprecated, please use normal object inheritance instead.\n" | ||||||
|     ''' |             "See more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#deprecations" | ||||||
| 
 |  | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         # Also ensure initialization is only performed for subclasses of |  | ||||||
|         # AbstractType |  | ||||||
|         if not is_base_type(bases, AbstractTypeMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         for base in bases: |  | ||||||
|             if not issubclass(base, AbstractType) and issubclass(type(base), AbstractTypeMeta): |  | ||||||
|                 # raise Exception('You can only extend AbstractTypes after the base definition.') |  | ||||||
|                 return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         base_fields = get_base_fields(bases, _as=None) |  | ||||||
| 
 |  | ||||||
|         fields = yank_fields_from_attrs(attrs, _as=None) |  | ||||||
| 
 |  | ||||||
|         options = Options( |  | ||||||
|             fields=merge(base_fields, fields) |  | ||||||
|         ) |         ) | ||||||
|         cls = type.__new__(cls, name, bases, dict(attrs, _meta=options)) |         super(AbstractType, cls).__init_subclass__(*args, **kwargs) | ||||||
| 
 |  | ||||||
|         return cls |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class AbstractType(six.with_metaclass(AbstractTypeMeta)): |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
| from itertools import chain | from itertools import chain | ||||||
| 
 | 
 | ||||||
|  | from .dynamic import Dynamic | ||||||
| from .mountedtype import MountedType | from .mountedtype import MountedType | ||||||
| from .structures import NonNull | from .structures import NonNull | ||||||
| from .dynamic import Dynamic |  | ||||||
| from .utils import get_type | from .utils import get_type | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								graphene/types/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								graphene/types/base.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | ||||||
|  | from ..utils.subclass_with_meta import SubclassWithMeta | ||||||
|  | from ..utils.trim_docstring import trim_docstring | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseOptions(object): | ||||||
|  |     name = None  # type: str | ||||||
|  |     description = None  # type: str | ||||||
|  | 
 | ||||||
|  |     _frozen = False  # type: bool | ||||||
|  | 
 | ||||||
|  |     def __init__(self, class_type): | ||||||
|  |         self.class_type = class_type  # type: Type | ||||||
|  | 
 | ||||||
|  |     def freeze(self): | ||||||
|  |         self._frozen = True | ||||||
|  | 
 | ||||||
|  |     def __setattr__(self, name, value): | ||||||
|  |         if not self._frozen: | ||||||
|  |             super(BaseOptions, self).__setattr__(name, value) | ||||||
|  |         else: | ||||||
|  |             raise Exception("Can't modify frozen Options {0}".format(self)) | ||||||
|  | 
 | ||||||
|  |     def __repr__(self): | ||||||
|  |         return "<{} type={}>".format(self.__class__.__name__, self.class_type.__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseType(SubclassWithMeta): | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def create_type(cls, class_name, **options): | ||||||
|  |         return type(class_name, (cls, ), {'Meta': options}) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, name=None, description=None, _meta=None): | ||||||
|  |         assert "_meta" not in cls.__dict__, "Can't assign directly meta" | ||||||
|  |         if not _meta: | ||||||
|  |             return | ||||||
|  |         _meta.name = name or cls.__name__ | ||||||
|  |         _meta.description = description or trim_docstring(cls.__doc__) | ||||||
|  |         _meta.freeze() | ||||||
|  |         cls._meta = _meta | ||||||
|  |         super(BaseType, cls).__init_subclass_with_meta__() | ||||||
							
								
								
									
										4
									
								
								graphene/types/context.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								graphene/types/context.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | class Context(object): | ||||||
|  |     def __init__(self, **params): | ||||||
|  |         for key, value in params.items(): | ||||||
|  |             setattr(self, key, value) | ||||||
|  | @ -2,15 +2,12 @@ from collections import OrderedDict | ||||||
| 
 | 
 | ||||||
| import six | import six | ||||||
| 
 | 
 | ||||||
| from ..utils.is_base_type import is_base_type | from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta | ||||||
| from ..utils.trim_docstring import trim_docstring | 
 | ||||||
| from .options import Options | from .base import BaseOptions, BaseType | ||||||
| from .unmountedtype import UnmountedType | from .unmountedtype import UnmountedType | ||||||
| 
 | 
 | ||||||
| try: | from ..pyutils.compat import Enum as PyEnum | ||||||
|     from enum import Enum as PyEnum |  | ||||||
| except ImportError: |  | ||||||
|     from ..pyutils.enum import Enum as PyEnum |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def eq_enum(self, other): | def eq_enum(self, other): | ||||||
|  | @ -19,29 +16,18 @@ def eq_enum(self, other): | ||||||
|     return self.value is other |     return self.value is other | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class EnumTypeMeta(type): | EnumType = type(PyEnum) | ||||||
| 
 | 
 | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         # Also ensure initialization is only performed for subclasses of Model |  | ||||||
|         # (excluding Model class itself). |  | ||||||
|         if not is_base_type(bases, EnumTypeMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 | 
 | ||||||
|         options = Options( | class EnumOptions(BaseOptions): | ||||||
|             attrs.pop('Meta', None), |     enum = None  # type: Enum | ||||||
|             name=name, |  | ||||||
|             description=trim_docstring(attrs.get('__doc__')), |  | ||||||
|             enum=None, |  | ||||||
|         ) |  | ||||||
|         if not options.enum: |  | ||||||
|             attrs['__eq__'] = eq_enum |  | ||||||
|             options.enum = PyEnum(cls.__name__, attrs) |  | ||||||
| 
 | 
 | ||||||
|         new_attrs = OrderedDict(attrs, _meta=options, **options.enum.__members__) |  | ||||||
|         return type.__new__(cls, name, bases, new_attrs) |  | ||||||
| 
 | 
 | ||||||
|     def __prepare__(name, bases, **kwargs):  # noqa: N805 | class EnumMeta(SubclassWithMeta_Meta): | ||||||
|         return OrderedDict() | 
 | ||||||
|  |     def __new__(cls, name, bases, classdict, **options): | ||||||
|  |         enum = PyEnum(cls.__name__, OrderedDict(classdict, __eq__=eq_enum)) | ||||||
|  |         return SubclassWithMeta_Meta.__new__(cls, name, bases, OrderedDict(classdict, __enum__=enum), **options) | ||||||
| 
 | 
 | ||||||
|     def get(cls, value): |     def get(cls, value): | ||||||
|         return cls._meta.enum(value) |         return cls._meta.enum(value) | ||||||
|  | @ -49,29 +35,30 @@ class EnumTypeMeta(type): | ||||||
|     def __getitem__(cls, value): |     def __getitem__(cls, value): | ||||||
|         return cls._meta.enum[value] |         return cls._meta.enum[value] | ||||||
| 
 | 
 | ||||||
|  |     def __prepare__(name, bases, **kwargs):  # noqa: N805 | ||||||
|  |         return OrderedDict() | ||||||
|  | 
 | ||||||
|     def __call__(cls, *args, **kwargs):  # noqa: N805 |     def __call__(cls, *args, **kwargs):  # noqa: N805 | ||||||
|         if cls is Enum: |         if cls is Enum: | ||||||
|             description = kwargs.pop('description', None) |             description = kwargs.pop('description', None) | ||||||
|             return cls.from_enum(PyEnum(*args, **kwargs), description=description) |             return cls.from_enum(PyEnum(*args, **kwargs), description=description) | ||||||
|         return super(EnumTypeMeta, cls).__call__(*args, **kwargs) |         return super(EnumMeta, cls).__call__(*args, **kwargs) | ||||||
|         # return cls._meta.enum(*args, **kwargs) |         # return cls._meta.enum(*args, **kwargs) | ||||||
| 
 | 
 | ||||||
|     def from_enum(cls, enum, description=None):  # noqa: N805 |     def from_enum(cls, enum, description=None):  # noqa: N805 | ||||||
|         meta_class = type('Meta', (object,), {'enum': enum, 'description': description}) |         meta_class = type('Meta', (object,), {'enum': enum, 'description': description}) | ||||||
|         return type(meta_class.enum.__name__, (Enum,), {'Meta': meta_class}) |         return type(meta_class.enum.__name__, (Enum,), {'Meta': meta_class}) | ||||||
| 
 | 
 | ||||||
|     def __str__(cls):  # noqa: N805 |  | ||||||
|         return cls._meta.name |  | ||||||
| 
 | 
 | ||||||
|  | class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)): | ||||||
| 
 | 
 | ||||||
| class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)): |     @classmethod | ||||||
|     ''' |     def __init_subclass_with_meta__(cls, enum=None, **options): | ||||||
|     Enum Type Definition |         _meta = EnumOptions(cls) | ||||||
| 
 |         _meta.enum = enum or cls.__enum__ | ||||||
|     Some leaf values of requests and input values are Enums. GraphQL serializes |         for key, value in _meta.enum.__members__.items(): | ||||||
|     Enum values as strings, however internally Enums can be represented by any |             setattr(cls, key, value) | ||||||
|     kind of type, often integers. |         super(Enum, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
|     ''' |  | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_type(cls): |     def get_type(cls): | ||||||
|  |  | ||||||
|  | @ -8,11 +8,10 @@ from .structures import NonNull | ||||||
| from .unmountedtype import UnmountedType | from .unmountedtype import UnmountedType | ||||||
| from .utils import get_type | from .utils import get_type | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| base_type = type | base_type = type | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def source_resolver(source, root, args, context, info): | def source_resolver(source, root, info, **args): | ||||||
|     resolved = getattr(root, source, None) |     resolved = getattr(root, source, None) | ||||||
|     if inspect.isfunction(resolved) or inspect.ismethod(resolved): |     if inspect.isfunction(resolved) or inspect.ismethod(resolved): | ||||||
|         return resolved() |         return resolved() | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| 
 | 
 | ||||||
|  | from graphene.types.scalars import MAX_INT, MIN_INT | ||||||
| from graphql.language.ast import (BooleanValue, FloatValue, IntValue, | from graphql.language.ast import (BooleanValue, FloatValue, IntValue, | ||||||
|                                   StringValue, ListValue, ObjectValue) |                                   ListValue, ObjectValue, StringValue) | ||||||
| 
 | 
 | ||||||
| from graphene.types.scalars import MIN_INT, MAX_INT |  | ||||||
| from .scalars import Scalar | from .scalars import Scalar | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,45 +1,36 @@ | ||||||
| import six | from collections import OrderedDict | ||||||
| 
 | 
 | ||||||
| from ..utils.is_base_type import is_base_type | from .base import BaseOptions, BaseType | ||||||
| from ..utils.trim_docstring import trim_docstring |  | ||||||
| from .abstracttype import AbstractTypeMeta |  | ||||||
| from .inputfield import InputField | from .inputfield import InputField | ||||||
| from .options import Options |  | ||||||
| from .unmountedtype import UnmountedType | from .unmountedtype import UnmountedType | ||||||
| from .utils import get_base_fields, merge, yank_fields_from_attrs | from .utils import yank_fields_from_attrs | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class InputObjectTypeMeta(AbstractTypeMeta): | # For static type checking with Mypy | ||||||
| 
 | MYPY = False | ||||||
|     def __new__(cls, name, bases, attrs): | if MYPY: | ||||||
|         # Also ensure initialization is only performed for subclasses of |     from typing import Dict, Callable  # NOQA | ||||||
|         # InputObjectType |  | ||||||
|         if not is_base_type(bases, InputObjectTypeMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         options = Options( |  | ||||||
|             attrs.pop('Meta', None), |  | ||||||
|             name=name, |  | ||||||
|             description=trim_docstring(attrs.get('__doc__')), |  | ||||||
|             local_fields=None, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         options.base_fields = get_base_fields(bases, _as=InputField) |  | ||||||
| 
 |  | ||||||
|         if not options.local_fields: |  | ||||||
|             options.local_fields = yank_fields_from_attrs(attrs, _as=InputField) |  | ||||||
| 
 |  | ||||||
|         options.fields = merge( |  | ||||||
|             options.base_fields, |  | ||||||
|             options.local_fields |  | ||||||
|         ) |  | ||||||
|         return type.__new__(cls, name, bases, dict(attrs, _meta=options)) |  | ||||||
| 
 |  | ||||||
|     def __str__(cls):  # noqa: N802 |  | ||||||
|         return cls._meta.name |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)): | class InputObjectTypeOptions(BaseOptions): | ||||||
|  |     fields = None  # type: Dict[str, InputField] | ||||||
|  |     create_container = None  # type: Callable | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class InputObjectTypeContainer(dict, BaseType): | ||||||
|  |     class Meta: | ||||||
|  |         abstract = True | ||||||
|  | 
 | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         dict.__init__(self, *args, **kwargs) | ||||||
|  |         for key, value in self.items(): | ||||||
|  |             setattr(self, key, value) | ||||||
|  | 
 | ||||||
|  |     def __init_subclass__(cls, *args, **kwargs): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class InputObjectType(UnmountedType, BaseType): | ||||||
|     ''' |     ''' | ||||||
|     Input Object Type Definition |     Input Object Type Definition | ||||||
| 
 | 
 | ||||||
|  | @ -49,6 +40,22 @@ class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)): | ||||||
|     Using `NonNull` will ensure that a value must be provided by the query |     Using `NonNull` will ensure that a value must be provided by the query | ||||||
|     ''' |     ''' | ||||||
| 
 | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, container=None, **options): | ||||||
|  |         _meta = InputObjectTypeOptions(cls) | ||||||
|  | 
 | ||||||
|  |         fields = OrderedDict() | ||||||
|  |         for base in reversed(cls.__mro__): | ||||||
|  |             fields.update( | ||||||
|  |                 yank_fields_from_attrs(base.__dict__, _as=InputField) | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         _meta.fields = fields | ||||||
|  |         if container is None: | ||||||
|  |             container = type(cls.__name__, (InputObjectTypeContainer, cls), {}) | ||||||
|  |         _meta.container = container | ||||||
|  |         super(InputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
|  | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_type(cls): |     def get_type(cls): | ||||||
|         ''' |         ''' | ||||||
|  |  | ||||||
|  | @ -1,45 +1,20 @@ | ||||||
| import six | from collections import OrderedDict | ||||||
| 
 | 
 | ||||||
| from ..utils.is_base_type import is_base_type | from .base import BaseOptions, BaseType | ||||||
| from ..utils.trim_docstring import trim_docstring |  | ||||||
| from .abstracttype import AbstractTypeMeta |  | ||||||
| from .field import Field | from .field import Field | ||||||
| from .options import Options | from .utils import yank_fields_from_attrs | ||||||
| from .utils import get_base_fields, merge, yank_fields_from_attrs | 
 | ||||||
|  | # For static type checking with Mypy | ||||||
|  | MYPY = False | ||||||
|  | if MYPY: | ||||||
|  |     from typing import Dict  # NOQA | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class InterfaceMeta(AbstractTypeMeta): | class InterfaceOptions(BaseOptions): | ||||||
| 
 |     fields = None  # type: Dict[str, Field] | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         # Also ensure initialization is only performed for subclasses of |  | ||||||
|         # Interface |  | ||||||
|         if not is_base_type(bases, InterfaceMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         options = Options( |  | ||||||
|             attrs.pop('Meta', None), |  | ||||||
|             name=name, |  | ||||||
|             description=trim_docstring(attrs.get('__doc__')), |  | ||||||
|             local_fields=None, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         options.base_fields = get_base_fields(bases, _as=Field) |  | ||||||
| 
 |  | ||||||
|         if not options.local_fields: |  | ||||||
|             options.local_fields = yank_fields_from_attrs(attrs, _as=Field) |  | ||||||
| 
 |  | ||||||
|         options.fields = merge( |  | ||||||
|             options.base_fields, |  | ||||||
|             options.local_fields |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         return type.__new__(cls, name, bases, dict(attrs, _meta=options)) |  | ||||||
| 
 |  | ||||||
|     def __str__(cls):  # noqa: N802 |  | ||||||
|         return cls._meta.name |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Interface(six.with_metaclass(InterfaceMeta)): | class Interface(BaseType): | ||||||
|     ''' |     ''' | ||||||
|     Interface Type Definition |     Interface Type Definition | ||||||
| 
 | 
 | ||||||
|  | @ -48,16 +23,29 @@ class Interface(six.with_metaclass(InterfaceMeta)): | ||||||
|     all types, as well as a function to determine which type is actually used |     all types, as well as a function to determine which type is actually used | ||||||
|     when the field is resolved. |     when the field is resolved. | ||||||
|     ''' |     ''' | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, _meta=None, **options): | ||||||
|  |         if not _meta: | ||||||
|  |             _meta = InterfaceOptions(cls) | ||||||
|  | 
 | ||||||
|  |         fields = OrderedDict() | ||||||
|  |         for base in reversed(cls.__mro__): | ||||||
|  |             fields.update( | ||||||
|  |                 yank_fields_from_attrs(base.__dict__, _as=Field) | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         if _meta.fields: | ||||||
|  |             _meta.fields.update(fields) | ||||||
|  |         else: | ||||||
|  |             _meta.fields = fields | ||||||
|  | 
 | ||||||
|  |         super(Interface, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def resolve_type(cls, instance, context, info): |     def resolve_type(cls, instance, info): | ||||||
|         from .objecttype import ObjectType |         from .objecttype import ObjectType | ||||||
|         if isinstance(instance, ObjectType): |         if isinstance(instance, ObjectType): | ||||||
|             return type(instance) |             return type(instance) | ||||||
| 
 | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         raise Exception("An Interface cannot be intitialized") |         raise Exception("An Interface cannot be intitialized") | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def implements(cls, objecttype): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|  | @ -1,33 +1,81 @@ | ||||||
| from functools import partial | from collections import OrderedDict | ||||||
| 
 | 
 | ||||||
| import six |  | ||||||
| 
 |  | ||||||
| from ..utils.is_base_type import is_base_type |  | ||||||
| from ..utils.get_unbound_function import get_unbound_function | from ..utils.get_unbound_function import get_unbound_function | ||||||
| from ..utils.props import props | from ..utils.props import props | ||||||
| from .field import Field | from .field import Field | ||||||
| from .objecttype import ObjectType, ObjectTypeMeta | from .objecttype import ObjectType, ObjectTypeOptions | ||||||
|  | from .utils import yank_fields_from_attrs | ||||||
|  | from ..utils.deprecated import warn_deprecation | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MutationMeta(ObjectTypeMeta): | # For static type checking with Mypy | ||||||
|     def __new__(cls, name, bases, attrs): | MYPY = False | ||||||
|         # Also ensure initialization is only performed for subclasses of | if MYPY: | ||||||
|         # Mutation |     from .argument import Argument  # NOQA | ||||||
|         if not is_base_type(bases, MutationMeta): |     from typing import Dict, Type, Callable  # NOQA | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         input_class = attrs.pop('Input', None) |  | ||||||
| 
 |  | ||||||
|         cls = ObjectTypeMeta.__new__(cls, name, bases, attrs) |  | ||||||
|         field_args = props(input_class) if input_class else {} |  | ||||||
|         output_class = getattr(cls, 'Output', cls) |  | ||||||
|         resolver = getattr(cls, 'mutate', None) |  | ||||||
|         assert resolver, 'All mutations must define a mutate method in it' |  | ||||||
|         resolver = get_unbound_function(resolver) |  | ||||||
|         cls.Field = partial( |  | ||||||
|             Field, output_class, args=field_args, resolver=resolver) |  | ||||||
|         return cls |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Mutation(six.with_metaclass(MutationMeta, ObjectType)): | class MutationOptions(ObjectTypeOptions): | ||||||
|     pass |     arguments = None  # type: Dict[str, Argument] | ||||||
|  |     output = None  # type: Type[ObjectType] | ||||||
|  |     resolver = None  # type: Callable | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Mutation(ObjectType): | ||||||
|  |     ''' | ||||||
|  |     Mutation Type Definition | ||||||
|  |     ''' | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, resolver=None, output=None, arguments=None, | ||||||
|  |                                     _meta=None, **options): | ||||||
|  |         if not _meta: | ||||||
|  |             _meta = MutationOptions(cls) | ||||||
|  | 
 | ||||||
|  |         output = output or getattr(cls, 'Output', None) | ||||||
|  |         fields = {} | ||||||
|  |         if not output: | ||||||
|  |             # If output is defined, we don't need to get the fields | ||||||
|  |             fields = OrderedDict() | ||||||
|  |             for base in reversed(cls.__mro__): | ||||||
|  |                 fields.update( | ||||||
|  |                     yank_fields_from_attrs(base.__dict__, _as=Field) | ||||||
|  |                 ) | ||||||
|  |             output = cls | ||||||
|  | 
 | ||||||
|  |         if not arguments: | ||||||
|  |             input_class = getattr(cls, 'Arguments', None) | ||||||
|  |             if not input_class: | ||||||
|  |                 input_class = getattr(cls, 'Input', None) | ||||||
|  |                 if input_class: | ||||||
|  |                     warn_deprecation(( | ||||||
|  |                         "Please use {name}.Arguments instead of {name}.Input." | ||||||
|  |                         "Input is now only used in ClientMutationID.\n" | ||||||
|  |                         "Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#mutation-input" | ||||||
|  |                     ).format(name=cls.__name__)) | ||||||
|  | 
 | ||||||
|  |             if input_class: | ||||||
|  |                 arguments = props(input_class) | ||||||
|  |             else: | ||||||
|  |                 arguments = {} | ||||||
|  | 
 | ||||||
|  |         if not resolver: | ||||||
|  |             mutate = getattr(cls, 'mutate', None) | ||||||
|  |             assert mutate, 'All mutations must define a mutate method in it' | ||||||
|  |             resolver = get_unbound_function(mutate) | ||||||
|  | 
 | ||||||
|  |         if _meta.fields: | ||||||
|  |             _meta.fields.update(fields) | ||||||
|  |         else: | ||||||
|  |             _meta.fields = fields | ||||||
|  | 
 | ||||||
|  |         _meta.output = output | ||||||
|  |         _meta.resolver = resolver | ||||||
|  |         _meta.arguments = arguments | ||||||
|  | 
 | ||||||
|  |         super(Mutation, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def Field(cls, *args, **kwargs): | ||||||
|  |         return Field( | ||||||
|  |             cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  | @ -1,82 +1,64 @@ | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
| 
 | 
 | ||||||
| import six | from .base import BaseOptions, BaseType | ||||||
| 
 |  | ||||||
| from ..utils.is_base_type import is_base_type |  | ||||||
| from ..utils.trim_docstring import trim_docstring |  | ||||||
| from .abstracttype import AbstractTypeMeta |  | ||||||
| from .field import Field | from .field import Field | ||||||
| from .interface import Interface | from .interface import Interface | ||||||
| from .options import Options | from .utils import yank_fields_from_attrs | ||||||
| from .utils import get_base_fields, merge, yank_fields_from_attrs | 
 | ||||||
|  | # For static type checking with Mypy | ||||||
|  | MYPY = False | ||||||
|  | if MYPY: | ||||||
|  |     from typing import Dict, Iterable, Type  # NOQA | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ObjectTypeMeta(AbstractTypeMeta): | class ObjectTypeOptions(BaseOptions): | ||||||
| 
 |     fields = None  # type: Dict[str, Field] | ||||||
|     def __new__(cls, name, bases, attrs): |     interfaces = ()  # type: Iterable[Type[Interface]] | ||||||
|         # Also ensure initialization is only performed for subclasses of |  | ||||||
|         # ObjectType |  | ||||||
|         if not is_base_type(bases, ObjectTypeMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         _meta = attrs.pop('_meta', None) |  | ||||||
|         defaults = dict( |  | ||||||
|             name=name, |  | ||||||
|             description=trim_docstring(attrs.get('__doc__')), |  | ||||||
|             interfaces=(), |  | ||||||
|             possible_types=(), |  | ||||||
|             default_resolver=None, |  | ||||||
|             local_fields=OrderedDict(), |  | ||||||
|         ) |  | ||||||
|         if not _meta: |  | ||||||
|             options = Options( |  | ||||||
|                 attrs.pop('Meta', None), |  | ||||||
|                 **defaults |  | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             options = _meta.extend_with_defaults(defaults) |  | ||||||
| 
 |  | ||||||
|         options.base_fields = get_base_fields(bases, _as=Field) |  | ||||||
| 
 |  | ||||||
|         if not options.local_fields: |  | ||||||
|             options.local_fields = yank_fields_from_attrs(attrs=attrs, _as=Field) |  | ||||||
| 
 |  | ||||||
|         options.interface_fields = OrderedDict() |  | ||||||
|         for interface in options.interfaces: |  | ||||||
|             assert issubclass(interface, Interface), ( |  | ||||||
|                 'All interfaces of {} must be a subclass of Interface. Received "{}".' |  | ||||||
|             ).format(name, interface) |  | ||||||
|             options.interface_fields.update(interface._meta.fields) |  | ||||||
| 
 |  | ||||||
|         options.fields = merge( |  | ||||||
|             options.interface_fields, |  | ||||||
|             options.base_fields, |  | ||||||
|             options.local_fields |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         cls = type.__new__(cls, name, bases, dict(attrs, _meta=options)) |  | ||||||
| 
 |  | ||||||
|         assert not (options.possible_types and cls.is_type_of), ( |  | ||||||
|             '{}.Meta.possible_types will cause type collision with {}.is_type_of. ' |  | ||||||
|             'Please use one or other.' |  | ||||||
|         ).format(name, name) |  | ||||||
| 
 |  | ||||||
|         for interface in options.interfaces: |  | ||||||
|             interface.implements(cls) |  | ||||||
| 
 |  | ||||||
|         return cls |  | ||||||
| 
 |  | ||||||
|     def __str__(cls):  # noqa: N802 |  | ||||||
|         return cls._meta.name |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ObjectType(six.with_metaclass(ObjectTypeMeta)): | class ObjectType(BaseType): | ||||||
|     ''' |     ''' | ||||||
|     Object Type Definition |     Object Type Definition | ||||||
| 
 | 
 | ||||||
|     Almost all of the GraphQL types you define will be object types. Object types |     Almost all of the GraphQL types you define will be object types. Object types | ||||||
|     have a name, but most importantly describe their fields. |     have a name, but most importantly describe their fields. | ||||||
|     ''' |     ''' | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__( | ||||||
|  |             cls, interfaces=(), | ||||||
|  |             possible_types=(), | ||||||
|  |             default_resolver=None, _meta=None, **options): | ||||||
|  |         if not _meta: | ||||||
|  |             _meta = ObjectTypeOptions(cls) | ||||||
|  | 
 | ||||||
|  |         fields = OrderedDict() | ||||||
|  | 
 | ||||||
|  |         for interface in interfaces: | ||||||
|  |             assert issubclass(interface, Interface), ( | ||||||
|  |                 'All interfaces of {} must be a subclass of Interface. Received "{}".' | ||||||
|  |             ).format(cls.__name__, interface) | ||||||
|  |             fields.update(interface._meta.fields) | ||||||
|  | 
 | ||||||
|  |         for base in reversed(cls.__mro__): | ||||||
|  |             fields.update( | ||||||
|  |                 yank_fields_from_attrs(base.__dict__, _as=Field) | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         assert not (possible_types and cls.is_type_of), ( | ||||||
|  |             '{name}.Meta.possible_types will cause type collision with {name}.is_type_of. ' | ||||||
|  |             'Please use one or other.' | ||||||
|  |         ).format(name=cls.__name__) | ||||||
|  | 
 | ||||||
|  |         if _meta.fields: | ||||||
|  |             _meta.fields.update(fields) | ||||||
|  |         else: | ||||||
|  |             _meta.fields = fields | ||||||
|  | 
 | ||||||
|  |         _meta.interfaces = interfaces | ||||||
|  |         _meta.possible_types = possible_types | ||||||
|  |         _meta.default_resolver = default_resolver | ||||||
|  | 
 | ||||||
|  |         super(ObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
| 
 | 
 | ||||||
|     is_type_of = None |     is_type_of = None | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,42 +0,0 @@ | ||||||
| import inspect |  | ||||||
| 
 |  | ||||||
| from ..utils.props import props |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Options(object): |  | ||||||
|     ''' |  | ||||||
|     This is the class wrapper around Meta. |  | ||||||
|     It helps to validate and cointain the attributes inside |  | ||||||
|     ''' |  | ||||||
| 
 |  | ||||||
|     def __init__(self, meta=None, **defaults): |  | ||||||
|         if meta: |  | ||||||
|             assert inspect.isclass(meta), ( |  | ||||||
|                 'Meta have to be a class, received "{}".'.format(repr(meta)) |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         meta_attrs = props(meta) if meta else {} |  | ||||||
|         for attr_name, value in defaults.items(): |  | ||||||
|             if attr_name in meta_attrs: |  | ||||||
|                 value = meta_attrs.pop(attr_name) |  | ||||||
|             setattr(self, attr_name, value) |  | ||||||
| 
 |  | ||||||
|         # If meta_attrs is not empty, it implicitly means |  | ||||||
|         # it received invalid attributes |  | ||||||
|         if meta_attrs: |  | ||||||
|             raise TypeError( |  | ||||||
|                 "Invalid attributes: {}".format( |  | ||||||
|                     ', '.join(sorted(meta_attrs.keys())) |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|     def extend_with_defaults(self, defaults): |  | ||||||
|         for attr_name, value in defaults.items(): |  | ||||||
|             if not hasattr(self, attr_name): |  | ||||||
|                 setattr(self, attr_name, value) |  | ||||||
|         return self |  | ||||||
| 
 |  | ||||||
|     def __repr__(self): |  | ||||||
|         options_props = props(self) |  | ||||||
|         props_as_attrs = ' '.join(['{}={}'.format(key, value) for key, value in options_props.items()]) |  | ||||||
|         return '<Options {}>'.format(props_as_attrs) |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| def attr_resolver(attname, default_value, root, args, context, info): | def attr_resolver(attname, default_value, root, info, **args): | ||||||
|     return getattr(root, attname, default_value) |     return getattr(root, attname, default_value) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def dict_resolver(attname, default_value, root, args, context, info): | def dict_resolver(attname, default_value, root, info, **args): | ||||||
|     return root.get(attname, default_value) |     return root.get(attname, default_value) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,34 +1,17 @@ | ||||||
| import six | import six | ||||||
|  | 
 | ||||||
| from graphql.language.ast import (BooleanValue, FloatValue, IntValue, | from graphql.language.ast import (BooleanValue, FloatValue, IntValue, | ||||||
|                                   StringValue) |                                   StringValue) | ||||||
| 
 | 
 | ||||||
| from ..utils.is_base_type import is_base_type | from .base import BaseOptions, BaseType | ||||||
| from ..utils.trim_docstring import trim_docstring |  | ||||||
| from .options import Options |  | ||||||
| from .unmountedtype import UnmountedType | from .unmountedtype import UnmountedType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ScalarTypeMeta(type): | class ScalarOptions(BaseOptions): | ||||||
| 
 |     pass | ||||||
|     def __new__(cls, name, bases, attrs): |  | ||||||
|         # Also ensure initialization is only performed for subclasses of Model |  | ||||||
|         # (excluding Model class itself). |  | ||||||
|         if not is_base_type(bases, ScalarTypeMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         options = Options( |  | ||||||
|             attrs.pop('Meta', None), |  | ||||||
|             name=name, |  | ||||||
|             description=trim_docstring(attrs.get('__doc__')), |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         return type.__new__(cls, name, bases, dict(attrs, _meta=options)) |  | ||||||
| 
 |  | ||||||
|     def __str__(cls):  # noqa: N802 |  | ||||||
|         return cls._meta.name |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)): | class Scalar(UnmountedType, BaseType): | ||||||
|     ''' |     ''' | ||||||
|     Scalar Type Definition |     Scalar Type Definition | ||||||
| 
 | 
 | ||||||
|  | @ -36,6 +19,10 @@ class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)): | ||||||
|     Scalars (or Enums) and are defined with a name and a series of functions |     Scalars (or Enums) and are defined with a name and a series of functions | ||||||
|     used to parse input from ast or variables and to ensure validity. |     used to parse input from ast or variables and to ensure validity. | ||||||
|     ''' |     ''' | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, **options): | ||||||
|  |         _meta = ScalarOptions(cls) | ||||||
|  |         super(Scalar, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
| 
 | 
 | ||||||
|     serialize = None |     serialize = None | ||||||
|     parse_value = None |     parse_value = None | ||||||
|  | @ -99,6 +86,7 @@ class Float(Scalar): | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def coerce_float(value): |     def coerce_float(value): | ||||||
|  |         # type: (Any) -> float | ||||||
|         try: |         try: | ||||||
|             return float(value) |             return float(value) | ||||||
|         except ValueError: |         except ValueError: | ||||||
|  |  | ||||||
|  | @ -68,9 +68,9 @@ class NonNull(Structure): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super(NonNull, self).__init__(*args, **kwargs) |         super(NonNull, self).__init__(*args, **kwargs) | ||||||
|         assert not isinstance(self.of_type, NonNull), ( |         assert not isinstance(self._of_type, NonNull), ( | ||||||
|             'Can only create NonNull of a Nullable GraphQLType but got: {}.' |             'Can only create NonNull of a Nullable GraphQLType but got: {}.' | ||||||
|         ).format(self.of_type) |         ).format(self._of_type) | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return '{}!'.format(self.of_type) |         return '{}!'.format(self.of_type) | ||||||
|  |  | ||||||
|  | @ -1,10 +1,13 @@ | ||||||
|  | import pytest | ||||||
| 
 | 
 | ||||||
| from ..abstracttype import AbstractType | from ..objecttype import ObjectType | ||||||
| from ..field import Field |  | ||||||
| from ..unmountedtype import UnmountedType | from ..unmountedtype import UnmountedType | ||||||
|  | from ..abstracttype import AbstractType | ||||||
|  | from .. import abstracttype | ||||||
|  | from ..field import Field | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MyType(object): | class MyType(ObjectType): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -14,29 +17,24 @@ class MyScalar(UnmountedType): | ||||||
|         return MyType |         return MyType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_abstracttype_with_fields(): | def test_abstract_objecttype_warn_deprecation(mocker): | ||||||
|  |     mocker.patch.object(abstracttype, 'warn_deprecation') | ||||||
|  | 
 | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(AbstractType): | ||||||
|         field = Field(MyType) |         field1 = MyScalar() | ||||||
| 
 | 
 | ||||||
|     assert 'field' in MyAbstractType._meta.fields |     assert abstracttype.warn_deprecation.called | ||||||
|     assert isinstance(MyAbstractType._meta.fields['field'], Field) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_abstracttype_with_unmountedfields(): | def test_generate_objecttype_inherit_abstracttype(): | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(AbstractType): | ||||||
|         field = UnmountedType(MyType) |         field1 = MyScalar() | ||||||
| 
 | 
 | ||||||
|     assert 'field' in MyAbstractType._meta.fields |     class MyObjectType(ObjectType, MyAbstractType): | ||||||
|     assert isinstance(MyAbstractType._meta.fields['field'], UnmountedType) |         field2 = MyScalar() | ||||||
| 
 | 
 | ||||||
| 
 |     assert MyObjectType._meta.description is None | ||||||
| def test_generate_abstracttype_inheritance(): |     assert MyObjectType._meta.interfaces == () | ||||||
|     class MyAbstractType1(AbstractType): |     assert MyObjectType._meta.name == "MyObjectType" | ||||||
|         field1 = UnmountedType(MyType) |     assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2'] | ||||||
| 
 |     assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field] | ||||||
|     class MyAbstractType2(MyAbstractType1): |  | ||||||
|         field2 = UnmountedType(MyType) |  | ||||||
| 
 |  | ||||||
|     assert list(MyAbstractType2._meta.fields.keys()) == ['field1', 'field2'] |  | ||||||
|     assert not hasattr(MyAbstractType1, 'field1') |  | ||||||
|     assert not hasattr(MyAbstractType2, 'field2') |  | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| import pytest |  | ||||||
| from functools import partial | from functools import partial | ||||||
| 
 | 
 | ||||||
|  | import pytest | ||||||
|  | 
 | ||||||
| from ..argument import Argument, to_arguments | from ..argument import Argument, to_arguments | ||||||
| from ..field import Field | from ..field import Field | ||||||
| from ..inputfield import InputField | from ..inputfield import InputField | ||||||
| from ..structures import NonNull |  | ||||||
| from ..scalars import String | from ..scalars import String | ||||||
|  | from ..structures import NonNull | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_argument(): | def test_argument(): | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								graphene/types/tests/test_base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								graphene/types/tests/test_base.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | ||||||
|  | import pytest | ||||||
|  | 
 | ||||||
|  | from ..base import BaseType, BaseOptions | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CustomOptions(BaseOptions): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CustomType(BaseType): | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, **options): | ||||||
|  |         _meta = CustomOptions(cls) | ||||||
|  |         super(CustomType, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_basetype(): | ||||||
|  |     class MyBaseType(CustomType): | ||||||
|  |         pass | ||||||
|  |      | ||||||
|  |     assert isinstance(MyBaseType._meta, CustomOptions) | ||||||
|  |     assert MyBaseType._meta.name == "MyBaseType" | ||||||
|  |     assert MyBaseType._meta.description is None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_basetype_nones(): | ||||||
|  |     class MyBaseType(CustomType): | ||||||
|  |         '''Documentation''' | ||||||
|  |         class Meta: | ||||||
|  |             name = None | ||||||
|  |             description = None | ||||||
|  | 
 | ||||||
|  |     assert isinstance(MyBaseType._meta, CustomOptions) | ||||||
|  |     assert MyBaseType._meta.name == "MyBaseType" | ||||||
|  |     assert MyBaseType._meta.description == "Documentation" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_basetype_custom(): | ||||||
|  |     class MyBaseType(CustomType): | ||||||
|  |         '''Documentation''' | ||||||
|  |         class Meta: | ||||||
|  |             name = 'Base' | ||||||
|  |             description = 'Desc' | ||||||
|  | 
 | ||||||
|  |     assert isinstance(MyBaseType._meta, CustomOptions) | ||||||
|  |     assert MyBaseType._meta.name == "Base" | ||||||
|  |     assert MyBaseType._meta.description == "Desc" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_basetype_create(): | ||||||
|  |     MyBaseType = CustomType.create_type('MyBaseType') | ||||||
|  | 
 | ||||||
|  |     assert isinstance(MyBaseType._meta, CustomOptions) | ||||||
|  |     assert MyBaseType._meta.name == "MyBaseType" | ||||||
|  |     assert MyBaseType._meta.description is None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_basetype_create_extra(): | ||||||
|  |     MyBaseType = CustomType.create_type('MyBaseType', name='Base', description='Desc') | ||||||
|  | 
 | ||||||
|  |     assert isinstance(MyBaseType._meta, CustomOptions) | ||||||
|  |     assert MyBaseType._meta.name == "Base" | ||||||
|  |     assert MyBaseType._meta.description == "Desc" | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| import datetime | import datetime | ||||||
|  | 
 | ||||||
| import pytz | import pytz | ||||||
| 
 | 
 | ||||||
| from ..datetime import DateTime, Time | from ..datetime import DateTime, Time | ||||||
|  | @ -10,12 +11,11 @@ class Query(ObjectType): | ||||||
|     datetime = DateTime(_in=DateTime(name='in')) |     datetime = DateTime(_in=DateTime(name='in')) | ||||||
|     time = Time(_at=Time(name='at')) |     time = Time(_at=Time(name='at')) | ||||||
| 
 | 
 | ||||||
|     def resolve_datetime(self, args, context, info): |     def resolve_datetime(self, info, _in=None): | ||||||
|         _in = args.get('_in') |  | ||||||
|         return _in |         return _in | ||||||
| 
 | 
 | ||||||
|     def resolve_time(self, args, context, info): |     def resolve_time(self, info, _at=None): | ||||||
|         return args.get('_at') |         return _at | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| schema = Schema(query=Query) | schema = Schema(query=Query) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| from ..abstracttype import AbstractType |  | ||||||
| from ..argument import Argument | from ..argument import Argument | ||||||
| from ..enum import Enum | from ..enum import Enum | ||||||
| from ..field import Field | from ..field import Field | ||||||
|  | @ -296,7 +295,7 @@ def test_stringifies_simple_types(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_does_not_mutate_passed_field_definitions(): | def test_does_not_mutate_passed_field_definitions(): | ||||||
|     class CommonFields(AbstractType): |     class CommonFields(object): | ||||||
|         field1 = String() |         field1 = String() | ||||||
|         field2 = String(id=String()) |         field2 = String(id=String()) | ||||||
| 
 | 
 | ||||||
|  | @ -307,12 +306,8 @@ def test_does_not_mutate_passed_field_definitions(): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|     assert TestObject1._meta.fields == TestObject2._meta.fields |     assert TestObject1._meta.fields == TestObject2._meta.fields | ||||||
|     assert CommonFields._meta.fields == { |  | ||||||
|         'field1': String(), |  | ||||||
|         'field2': String(id=String()), |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     class CommonFields(AbstractType): |     class CommonFields(object): | ||||||
|         field1 = String() |         field1 = String() | ||||||
|         field2 = String() |         field2 = String() | ||||||
| 
 | 
 | ||||||
|  | @ -323,8 +318,3 @@ def test_does_not_mutate_passed_field_definitions(): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|     assert TestInputObject1._meta.fields == TestInputObject2._meta.fields |     assert TestInputObject1._meta.fields == TestInputObject2._meta.fields | ||||||
| 
 |  | ||||||
|     assert CommonFields._meta.fields == { |  | ||||||
|         'field1': String(), |  | ||||||
|         'field2': String(), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| from ..structures import List, NonNull |  | ||||||
| from ..scalars import String |  | ||||||
| from ..dynamic import Dynamic | from ..dynamic import Dynamic | ||||||
|  | from ..scalars import String | ||||||
|  | from ..structures import List, NonNull | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dynamic(): | def test_dynamic(): | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
|  | from ..argument import Argument | ||||||
| from ..enum import Enum, PyEnum | from ..enum import Enum, PyEnum | ||||||
| from ..field import Field | from ..field import Field | ||||||
| from ..inputfield import InputField | from ..inputfield import InputField | ||||||
| from ..argument import Argument |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_enum_construction(): | def test_enum_construction(): | ||||||
|  |  | ||||||
|  | @ -1,10 +1,11 @@ | ||||||
| import pytest |  | ||||||
| from functools import partial | from functools import partial | ||||||
| 
 | 
 | ||||||
|  | import pytest | ||||||
|  | 
 | ||||||
| from ..argument import Argument | from ..argument import Argument | ||||||
| from ..field import Field | from ..field import Field | ||||||
| from ..structures import NonNull |  | ||||||
| from ..scalars import String | from ..scalars import String | ||||||
|  | from ..structures import NonNull | ||||||
| from .utils import MyLazyType | from .utils import MyLazyType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -19,10 +20,11 @@ class MyInstance(object): | ||||||
| def test_field_basic(): | def test_field_basic(): | ||||||
|     MyType = object() |     MyType = object() | ||||||
|     args = {'my arg': Argument(True)} |     args = {'my arg': Argument(True)} | ||||||
|     resolver = lambda: None | 
 | ||||||
|  |     def resolver(): return None | ||||||
|     deprecation_reason = 'Deprecated now' |     deprecation_reason = 'Deprecated now' | ||||||
|     description = 'My Field' |     description = 'My Field' | ||||||
|     my_default='something' |     my_default = 'something' | ||||||
|     field = Field( |     field = Field( | ||||||
|         MyType, |         MyType, | ||||||
|         name='name', |         name='name', | ||||||
|  | @ -59,7 +61,7 @@ def test_field_default_value_not_callable(): | ||||||
| def test_field_source(): | def test_field_source(): | ||||||
|     MyType = object() |     MyType = object() | ||||||
|     field = Field(MyType, source='value') |     field = Field(MyType, source='value') | ||||||
|     assert field.resolver(MyInstance, {}, None, None) == MyInstance.value |     assert field.resolver(MyInstance(), None) == MyInstance.value | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_field_with_lazy_type(): | def test_field_with_lazy_type(): | ||||||
|  | @ -83,19 +85,20 @@ def test_field_not_source_and_resolver(): | ||||||
|     MyType = object() |     MyType = object() | ||||||
|     with pytest.raises(Exception) as exc_info: |     with pytest.raises(Exception) as exc_info: | ||||||
|         Field(MyType, source='value', resolver=lambda: None) |         Field(MyType, source='value', resolver=lambda: None) | ||||||
|     assert str(exc_info.value) == 'A Field cannot have a source and a resolver in at the same time.' |     assert str( | ||||||
|  |         exc_info.value) == 'A Field cannot have a source and a resolver in at the same time.' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_field_source_func(): | def test_field_source_func(): | ||||||
|     MyType = object() |     MyType = object() | ||||||
|     field = Field(MyType, source='value_func') |     field = Field(MyType, source='value_func') | ||||||
|     assert field.resolver(MyInstance(), {}, None, None) == MyInstance.value_func() |     assert field.resolver(MyInstance(), None) == MyInstance.value_func() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_field_source_method(): | def test_field_source_method(): | ||||||
|     MyType = object() |     MyType = object() | ||||||
|     field = Field(MyType, source='value_method') |     field = Field(MyType, source='value_method') | ||||||
|     assert field.resolver(MyInstance(), {}, None, None) == MyInstance().value_method() |     assert field.resolver(MyInstance(), None) == MyInstance().value_method() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_field_source_as_argument(): | def test_field_source_as_argument(): | ||||||
|  |  | ||||||
|  | @ -6,8 +6,7 @@ from ..schema import Schema | ||||||
| class Query(ObjectType): | class Query(ObjectType): | ||||||
|     generic = GenericScalar(input=GenericScalar()) |     generic = GenericScalar(input=GenericScalar()) | ||||||
| 
 | 
 | ||||||
|     def resolve_generic(self, args, context, info): |     def resolve_generic(self, info, input=None): | ||||||
|         input = args.get('input') |  | ||||||
|         return input |         return input | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import pytest |  | ||||||
| from functools import partial | from functools import partial | ||||||
| 
 | 
 | ||||||
| from ..inputfield import InputField | from ..inputfield import InputField | ||||||
|  |  | ||||||
|  | @ -1,10 +1,9 @@ | ||||||
| 
 | 
 | ||||||
| from ..abstracttype import AbstractType |  | ||||||
| from ..field import Field |  | ||||||
| from ..argument import Argument | from ..argument import Argument | ||||||
|  | from ..field import Field | ||||||
| from ..inputfield import InputField | from ..inputfield import InputField | ||||||
| from ..objecttype import ObjectType |  | ||||||
| from ..inputobjecttype import InputObjectType | from ..inputobjecttype import InputObjectType | ||||||
|  | from ..objecttype import ObjectType | ||||||
| from ..unmountedtype import UnmountedType | from ..unmountedtype import UnmountedType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -80,7 +79,7 @@ def test_generate_inputobjecttype_as_argument(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_inputobjecttype_inherit_abstracttype(): | def test_generate_inputobjecttype_inherit_abstracttype(): | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(object): | ||||||
|         field1 = MyScalar(MyType) |         field1 = MyScalar(MyType) | ||||||
| 
 | 
 | ||||||
|     class MyInputObjectType(InputObjectType, MyAbstractType): |     class MyInputObjectType(InputObjectType, MyAbstractType): | ||||||
|  | @ -91,7 +90,7 @@ def test_generate_inputobjecttype_inherit_abstracttype(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_inputobjecttype_inherit_abstracttype_reversed(): | def test_generate_inputobjecttype_inherit_abstracttype_reversed(): | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(object): | ||||||
|         field1 = MyScalar(MyType) |         field1 = MyScalar(MyType) | ||||||
| 
 | 
 | ||||||
|     class MyInputObjectType(MyAbstractType, InputObjectType): |     class MyInputObjectType(MyAbstractType, InputObjectType): | ||||||
|  |  | ||||||
|  | @ -1,5 +1,3 @@ | ||||||
| 
 |  | ||||||
| from ..abstracttype import AbstractType |  | ||||||
| from ..field import Field | from ..field import Field | ||||||
| from ..interface import Interface | from ..interface import Interface | ||||||
| from ..unmountedtype import UnmountedType | from ..unmountedtype import UnmountedType | ||||||
|  | @ -61,7 +59,7 @@ def test_generate_interface_unmountedtype(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_interface_inherit_abstracttype(): | def test_generate_interface_inherit_abstracttype(): | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(object): | ||||||
|         field1 = MyScalar() |         field1 = MyScalar() | ||||||
| 
 | 
 | ||||||
|     class MyInterface(Interface, MyAbstractType): |     class MyInterface(Interface, MyAbstractType): | ||||||
|  | @ -84,7 +82,7 @@ def test_generate_interface_inherit_interface(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_interface_inherit_abstracttype_reversed(): | def test_generate_interface_inherit_abstracttype_reversed(): | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(object): | ||||||
|         field1 = MyScalar() |         field1 = MyScalar() | ||||||
| 
 | 
 | ||||||
|     class MyInterface(MyAbstractType, Interface): |     class MyInterface(MyAbstractType, Interface): | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import json |  | ||||||
| 
 | 
 | ||||||
| from ..json import JSONString | from ..json import JSONString | ||||||
| from ..objecttype import ObjectType | from ..objecttype import ObjectType | ||||||
|  | @ -8,8 +7,7 @@ from ..schema import Schema | ||||||
| class Query(ObjectType): | class Query(ObjectType): | ||||||
|     json = JSONString(input=JSONString()) |     json = JSONString(input=JSONString()) | ||||||
| 
 | 
 | ||||||
|     def resolve_json(self, args, context, info): |     def resolve_json(self, info, input): | ||||||
|         input = args.get('input') |  | ||||||
|         return input |         return input | ||||||
| 
 | 
 | ||||||
| schema = Schema(query=Query) | schema = Schema(query=Query) | ||||||
|  | @ -19,7 +17,7 @@ def test_jsonstring_query(): | ||||||
|     json_value = '{"key": "value"}' |     json_value = '{"key": "value"}' | ||||||
| 
 | 
 | ||||||
|     json_value_quoted = json_value.replace('"', '\\"') |     json_value_quoted = json_value.replace('"', '\\"') | ||||||
|     result = schema.execute('''{ json(input: "%s") }'''%json_value_quoted) |     result = schema.execute('''{ json(input: "%s") }''' % json_value_quoted) | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == { |     assert result.data == { | ||||||
|         'json': json_value |         'json': json_value | ||||||
|  |  | ||||||
|  | @ -1,11 +1,10 @@ | ||||||
| import pytest |  | ||||||
| 
 | 
 | ||||||
| from ..mountedtype import MountedType |  | ||||||
| from ..field import Field | from ..field import Field | ||||||
| from ..scalars import String | from ..scalars import String | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CustomField(Field): | class CustomField(Field): | ||||||
|  | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.metadata = kwargs.pop('metadata', None) |         self.metadata = kwargs.pop('metadata', None) | ||||||
|         super(CustomField, self).__init__(*args, **kwargs) |         super(CustomField, self).__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|  | @ -1,40 +1,41 @@ | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
|  | from ..argument import Argument | ||||||
|  | from ..dynamic import Dynamic | ||||||
| from ..mutation import Mutation | from ..mutation import Mutation | ||||||
| from ..objecttype import ObjectType | from ..objecttype import ObjectType | ||||||
| from ..schema import Schema |  | ||||||
| from ..argument import Argument |  | ||||||
| from ..scalars import String | from ..scalars import String | ||||||
| from ..dynamic import Dynamic | from ..schema import Schema | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_mutation_no_args(): | def test_generate_mutation_no_args(): | ||||||
|     class MyMutation(Mutation): |     class MyMutation(Mutation): | ||||||
|         '''Documentation''' |         '''Documentation''' | ||||||
| 
 | 
 | ||||||
|         @classmethod |         def mutate(self, info, **args): | ||||||
|         def mutate(cls, *args, **kwargs): |             return args | ||||||
|             pass |  | ||||||
| 
 | 
 | ||||||
|     assert issubclass(MyMutation, ObjectType) |     assert issubclass(MyMutation, ObjectType) | ||||||
|     assert MyMutation._meta.name == "MyMutation" |     assert MyMutation._meta.name == "MyMutation" | ||||||
|     assert MyMutation._meta.description == "Documentation" |     assert MyMutation._meta.description == "Documentation" | ||||||
|     assert MyMutation.Field().resolver == MyMutation.mutate |     resolved = MyMutation.Field().resolver(None, None, name='Peter') | ||||||
|  |     assert resolved == {'name': 'Peter'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_mutation_with_meta(): | def test_generate_mutation_with_meta(): | ||||||
|     class MyMutation(Mutation): |     class MyMutation(Mutation): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             name = 'MyOtherMutation' |             name = 'MyOtherMutation' | ||||||
|             description = 'Documentation' |             description = 'Documentation' | ||||||
| 
 | 
 | ||||||
|         @classmethod |         def mutate(self, info, **args): | ||||||
|         def mutate(cls, *args, **kwargs): |             return args | ||||||
|             pass |  | ||||||
| 
 | 
 | ||||||
|     assert MyMutation._meta.name == "MyOtherMutation" |     assert MyMutation._meta.name == "MyOtherMutation" | ||||||
|     assert MyMutation._meta.description == "Documentation" |     assert MyMutation._meta.description == "Documentation" | ||||||
|     assert MyMutation.Field().resolver == MyMutation.mutate |     resolved = MyMutation.Field().resolver(None, None, name='Peter') | ||||||
|  |     assert resolved == {'name': 'Peter'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_mutation_raises_exception_if_no_mutate(): | def test_mutation_raises_exception_if_no_mutate(): | ||||||
|  | @ -52,24 +53,26 @@ def test_mutation_custom_output_type(): | ||||||
|         name = String() |         name = String() | ||||||
| 
 | 
 | ||||||
|     class CreateUser(Mutation): |     class CreateUser(Mutation): | ||||||
|  | 
 | ||||||
|         class Input: |         class Input: | ||||||
|             name = String() |             name = String() | ||||||
| 
 | 
 | ||||||
|         Output = User |         Output = User | ||||||
| 
 | 
 | ||||||
|         @classmethod |         def mutate(self, info, name): | ||||||
|         def mutate(cls, args, context, info): |  | ||||||
|             name = args.get('name') |  | ||||||
|             return User(name=name) |             return User(name=name) | ||||||
| 
 | 
 | ||||||
|     field = CreateUser.Field() |     field = CreateUser.Field() | ||||||
|     assert field.type == User |     assert field.type == User | ||||||
|     assert field.args == {'name': Argument(String)} |     assert field.args == {'name': Argument(String)} | ||||||
|     assert field.resolver == CreateUser.mutate |     resolved = field.resolver(None, None, name='Peter') | ||||||
|  |     assert isinstance(resolved, User) | ||||||
|  |     assert resolved.name == 'Peter' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_mutation_execution(): | def test_mutation_execution(): | ||||||
|     class CreateUser(Mutation): |     class CreateUser(Mutation): | ||||||
|  | 
 | ||||||
|         class Input: |         class Input: | ||||||
|             name = String() |             name = String() | ||||||
|             dynamic = Dynamic(lambda: String()) |             dynamic = Dynamic(lambda: String()) | ||||||
|  | @ -78,9 +81,7 @@ def test_mutation_execution(): | ||||||
|         name = String() |         name = String() | ||||||
|         dynamic = Dynamic(lambda: String()) |         dynamic = Dynamic(lambda: String()) | ||||||
| 
 | 
 | ||||||
|         def mutate(self, args, context, info): |         def mutate(self, info, name, dynamic): | ||||||
|             name = args.get('name') |  | ||||||
|             dynamic = args.get('dynamic') |  | ||||||
|             return CreateUser(name=name, dynamic=dynamic) |             return CreateUser(name=name, dynamic=dynamic) | ||||||
| 
 | 
 | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
| from ..abstracttype import AbstractType |  | ||||||
| from ..field import Field | from ..field import Field | ||||||
| from ..interface import Interface | from ..interface import Interface | ||||||
| from ..objecttype import ObjectType | from ..objecttype import ObjectType | ||||||
|  | @ -89,7 +88,7 @@ def test_ordered_fields_in_objecttype(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_objecttype_inherit_abstracttype(): | def test_generate_objecttype_inherit_abstracttype(): | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(object): | ||||||
|         field1 = MyScalar() |         field1 = MyScalar() | ||||||
| 
 | 
 | ||||||
|     class MyObjectType(ObjectType, MyAbstractType): |     class MyObjectType(ObjectType, MyAbstractType): | ||||||
|  | @ -103,7 +102,7 @@ def test_generate_objecttype_inherit_abstracttype(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_generate_objecttype_inherit_abstracttype_reversed(): | def test_generate_objecttype_inherit_abstracttype_reversed(): | ||||||
|     class MyAbstractType(AbstractType): |     class MyAbstractType(object): | ||||||
|         field1 = MyScalar() |         field1 = MyScalar() | ||||||
| 
 | 
 | ||||||
|     class MyObjectType(MyAbstractType, ObjectType): |     class MyObjectType(MyAbstractType, ObjectType): | ||||||
|  | @ -188,6 +187,7 @@ def test_generate_objecttype_description(): | ||||||
| 
 | 
 | ||||||
| def test_objecttype_with_possible_types(): | def test_objecttype_with_possible_types(): | ||||||
|     class MyObjectType(ObjectType): |     class MyObjectType(ObjectType): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             possible_types = (dict, ) |             possible_types = (dict, ) | ||||||
| 
 | 
 | ||||||
|  | @ -197,6 +197,7 @@ def test_objecttype_with_possible_types(): | ||||||
| def test_objecttype_with_possible_types_and_is_type_of_should_raise(): | def test_objecttype_with_possible_types_and_is_type_of_should_raise(): | ||||||
|     with pytest.raises(AssertionError) as excinfo: |     with pytest.raises(AssertionError) as excinfo: | ||||||
|         class MyObjectType(ObjectType): |         class MyObjectType(ObjectType): | ||||||
|  | 
 | ||||||
|             class Meta: |             class Meta: | ||||||
|                 possible_types = (dict, ) |                 possible_types = (dict, ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,30 +0,0 @@ | ||||||
| import pytest |  | ||||||
| 
 |  | ||||||
| from ..options import Options |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_options(): |  | ||||||
|     class BaseOptions: |  | ||||||
|         option_1 = False |  | ||||||
|         name = True |  | ||||||
|     meta = Options(BaseOptions, name=False, option_1=False) |  | ||||||
|     assert meta.name == True |  | ||||||
|     assert meta.option_1 == False |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_options_extra_attrs(): |  | ||||||
|     class BaseOptions: |  | ||||||
|         name = True |  | ||||||
|         type = True |  | ||||||
|      |  | ||||||
|     with pytest.raises(Exception) as exc_info: |  | ||||||
|         meta = Options(BaseOptions) |  | ||||||
| 
 |  | ||||||
|     assert str(exc_info.value) == 'Invalid attributes: name, type' |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_options_repr(): |  | ||||||
|     class BaseOptions: |  | ||||||
|         name = True |  | ||||||
|     meta = Options(BaseOptions, name=False) |  | ||||||
|     assert repr(meta) == '<Options name=True>' |  | ||||||
|  | @ -1,18 +1,19 @@ | ||||||
| import json | import json | ||||||
| from functools import partial | from functools import partial | ||||||
| 
 | 
 | ||||||
| from graphql import Source, execute, parse, GraphQLError | from graphql import GraphQLError, Source, execute, parse, ResolveInfo | ||||||
| 
 | 
 | ||||||
|  | from ..dynamic import Dynamic | ||||||
| from ..field import Field | from ..field import Field | ||||||
| from ..interface import Interface |  | ||||||
| from ..inputfield import InputField | from ..inputfield import InputField | ||||||
| from ..inputobjecttype import InputObjectType | from ..inputobjecttype import InputObjectType | ||||||
|  | from ..interface import Interface | ||||||
| from ..objecttype import ObjectType | from ..objecttype import ObjectType | ||||||
| from ..scalars import Int, String | from ..scalars import Int, String | ||||||
| from ..schema import Schema | from ..schema import Schema | ||||||
| from ..structures import List | from ..structures import List | ||||||
| from ..union import Union | from ..union import Union | ||||||
| from ..dynamic import Dynamic | from ..context import Context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_query(): | def test_query(): | ||||||
|  | @ -26,6 +27,23 @@ def test_query(): | ||||||
|     assert executed.data == {'hello': 'World'} |     assert executed.data == {'hello': 'World'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_query_source(): | ||||||
|  |     class Root(object): | ||||||
|  |         _hello = "World" | ||||||
|  | 
 | ||||||
|  |         def hello(self): | ||||||
|  |             return self._hello | ||||||
|  | 
 | ||||||
|  |     class Query(ObjectType): | ||||||
|  |         hello = String(source="hello") | ||||||
|  | 
 | ||||||
|  |     hello_schema = Schema(Query) | ||||||
|  | 
 | ||||||
|  |     executed = hello_schema.execute('{ hello }', Root()) | ||||||
|  |     assert not executed.errors | ||||||
|  |     assert executed.data == {'hello': 'World'} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_query_union(): | def test_query_union(): | ||||||
|     class one_object(object): |     class one_object(object): | ||||||
|         pass |         pass | ||||||
|  | @ -37,24 +55,25 @@ def test_query_union(): | ||||||
|         one = String() |         one = String() | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def is_type_of(cls, root, context, info): |         def is_type_of(cls, root, info): | ||||||
|             return isinstance(root, one_object) |             return isinstance(root, one_object) | ||||||
| 
 | 
 | ||||||
|     class Two(ObjectType): |     class Two(ObjectType): | ||||||
|         two = String() |         two = String() | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def is_type_of(cls, root, context, info): |         def is_type_of(cls, root, info): | ||||||
|             return isinstance(root, two_object) |             return isinstance(root, two_object) | ||||||
| 
 | 
 | ||||||
|     class MyUnion(Union): |     class MyUnion(Union): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             types = (One, Two) |             types = (One, Two) | ||||||
| 
 | 
 | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         unions = List(MyUnion) |         unions = List(MyUnion) | ||||||
| 
 | 
 | ||||||
|         def resolve_unions(self, args, context, info): |         def resolve_unions(self, info): | ||||||
|             return [one_object(), two_object()] |             return [one_object(), two_object()] | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
|  | @ -81,29 +100,31 @@ def test_query_interface(): | ||||||
|         base = String() |         base = String() | ||||||
| 
 | 
 | ||||||
|     class One(ObjectType): |     class One(ObjectType): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             interfaces = (MyInterface, ) |             interfaces = (MyInterface, ) | ||||||
| 
 | 
 | ||||||
|         one = String() |         one = String() | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def is_type_of(cls, root, context, info): |         def is_type_of(cls, root, info): | ||||||
|             return isinstance(root, one_object) |             return isinstance(root, one_object) | ||||||
| 
 | 
 | ||||||
|     class Two(ObjectType): |     class Two(ObjectType): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             interfaces = (MyInterface, ) |             interfaces = (MyInterface, ) | ||||||
| 
 | 
 | ||||||
|         two = String() |         two = String() | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def is_type_of(cls, root, context, info): |         def is_type_of(cls, root, info): | ||||||
|             return isinstance(root, two_object) |             return isinstance(root, two_object) | ||||||
| 
 | 
 | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         interfaces = List(MyInterface) |         interfaces = List(MyInterface) | ||||||
| 
 | 
 | ||||||
|         def resolve_interfaces(self, args, context, info): |         def resolve_interfaces(self, info): | ||||||
|             return [one_object(), two_object()] |             return [one_object(), two_object()] | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query, types=[One, Two]) |     hello_schema = Schema(Query, types=[One, Two]) | ||||||
|  | @ -123,13 +144,15 @@ def test_query_dynamic(): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         hello = Dynamic(lambda: String(resolver=lambda *_: 'World')) |         hello = Dynamic(lambda: String(resolver=lambda *_: 'World')) | ||||||
|         hellos = Dynamic(lambda: List(String, resolver=lambda *_: ['Worlds'])) |         hellos = Dynamic(lambda: List(String, resolver=lambda *_: ['Worlds'])) | ||||||
|         hello_field = Dynamic(lambda: Field(String, resolver=lambda *_: 'Field World')) |         hello_field = Dynamic(lambda: Field( | ||||||
|  |             String, resolver=lambda *_: 'Field World')) | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
| 
 | 
 | ||||||
|     executed = hello_schema.execute('{ hello hellos helloField }') |     executed = hello_schema.execute('{ hello hellos helloField }') | ||||||
|     assert not executed.errors |     assert not executed.errors | ||||||
|     assert executed.data == {'hello': 'World', 'hellos': ['Worlds'], 'helloField': 'Field World'} |     assert executed.data == {'hello': 'World', 'hellos': [ | ||||||
|  |         'Worlds'], 'helloField': 'Field World'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_query_default_value(): | def test_query_default_value(): | ||||||
|  | @ -151,7 +174,7 @@ def test_query_wrong_default_value(): | ||||||
|         field = String() |         field = String() | ||||||
| 
 | 
 | ||||||
|         @classmethod |         @classmethod | ||||||
|         def is_type_of(cls, root, context, info): |         def is_type_of(cls, root, info): | ||||||
|             return isinstance(root, MyType) |             return isinstance(root, MyType) | ||||||
| 
 | 
 | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|  | @ -161,7 +184,8 @@ def test_query_wrong_default_value(): | ||||||
| 
 | 
 | ||||||
|     executed = hello_schema.execute('{ hello { field } }') |     executed = hello_schema.execute('{ hello { field } }') | ||||||
|     assert len(executed.errors) == 1 |     assert len(executed.errors) == 1 | ||||||
|     assert executed.errors[0].message == GraphQLError('Expected value of type "MyType" but got: str.').message |     assert executed.errors[0].message == GraphQLError( | ||||||
|  |         'Expected value of type "MyType" but got: str.').message | ||||||
|     assert executed.data == {'hello': None} |     assert executed.data == {'hello': None} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -170,7 +194,8 @@ def test_query_default_value_ignored_by_resolver(): | ||||||
|         field = String() |         field = String() | ||||||
| 
 | 
 | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         hello = Field(MyType, default_value='hello', resolver=lambda *_: MyType(field='no default.')) |         hello = Field(MyType, default_value='hello', | ||||||
|  |                       resolver=lambda *_: MyType(field='no default.')) | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
| 
 | 
 | ||||||
|  | @ -183,7 +208,7 @@ def test_query_resolve_function(): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         hello = String() |         hello = String() | ||||||
| 
 | 
 | ||||||
|         def resolve_hello(self, args, context, info): |         def resolve_hello(self, info): | ||||||
|             return 'World' |             return 'World' | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
|  | @ -197,7 +222,7 @@ def test_query_arguments(): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         test = String(a_str=String(), a_int=Int()) |         test = String(a_str=String(), a_int=Int()) | ||||||
| 
 | 
 | ||||||
|         def resolve_test(self, args, context, info): |         def resolve_test(self, info, **args): | ||||||
|             return json.dumps([self, args], separators=(',', ':')) |             return json.dumps([self, args], separators=(',', ':')) | ||||||
| 
 | 
 | ||||||
|     test_schema = Schema(Query) |     test_schema = Schema(Query) | ||||||
|  | @ -210,7 +235,8 @@ def test_query_arguments(): | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == {'test': '["Source!",{"a_str":"String!"}]'} |     assert result.data == {'test': '["Source!",{"a_str":"String!"}]'} | ||||||
| 
 | 
 | ||||||
|     result = test_schema.execute('{ test(aInt: -123, aStr: "String!") }', 'Source!') |     result = test_schema.execute( | ||||||
|  |         '{ test(aInt: -123, aStr: "String!") }', 'Source!') | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data in [ |     assert result.data in [ | ||||||
|         {'test': '["Source!",{"a_str":"String!","a_int":-123}]'}, |         {'test': '["Source!",{"a_str":"String!","a_int":-123}]'}, | ||||||
|  | @ -226,7 +252,7 @@ def test_query_input_field(): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         test = String(a_input=Input()) |         test = String(a_input=Input()) | ||||||
| 
 | 
 | ||||||
|         def resolve_test(self, args, context, info): |         def resolve_test(self, info, **args): | ||||||
|             return json.dumps([self, args], separators=(',', ':')) |             return json.dumps([self, args], separators=(',', ':')) | ||||||
| 
 | 
 | ||||||
|     test_schema = Schema(Query) |     test_schema = Schema(Query) | ||||||
|  | @ -235,13 +261,17 @@ def test_query_input_field(): | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == {'test': '[null,{}]'} |     assert result.data == {'test': '[null,{}]'} | ||||||
| 
 | 
 | ||||||
|     result = test_schema.execute('{ test(aInput: {aField: "String!"} ) }', 'Source!') |     result = test_schema.execute( | ||||||
|  |         '{ test(aInput: {aField: "String!"} ) }', 'Source!') | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == {'test': '["Source!",{"a_input":{"a_field":"String!"}}]'} |     assert result.data == { | ||||||
|  |         'test': '["Source!",{"a_input":{"a_field":"String!"}}]'} | ||||||
| 
 | 
 | ||||||
|     result = test_schema.execute('{ test(aInput: {recursiveField: {aField: "String!"}}) }', 'Source!') |     result = test_schema.execute( | ||||||
|  |         '{ test(aInput: {recursiveField: {aField: "String!"}}) }', 'Source!') | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == {'test': '["Source!",{"a_input":{"recursive_field":{"a_field":"String!"}}}]'} |     assert result.data == { | ||||||
|  |         'test': '["Source!",{"a_input":{"recursive_field":{"a_field":"String!"}}}]'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_query_middlewares(): | def test_query_middlewares(): | ||||||
|  | @ -249,10 +279,10 @@ def test_query_middlewares(): | ||||||
|         hello = String() |         hello = String() | ||||||
|         other = String() |         other = String() | ||||||
| 
 | 
 | ||||||
|         def resolve_hello(self, args, context, info): |         def resolve_hello(self, info): | ||||||
|             return 'World' |             return 'World' | ||||||
| 
 | 
 | ||||||
|         def resolve_other(self, args, context, info): |         def resolve_other(self, info): | ||||||
|             return 'other' |             return 'other' | ||||||
| 
 | 
 | ||||||
|     def reversed_middleware(next, *args, **kwargs): |     def reversed_middleware(next, *args, **kwargs): | ||||||
|  | @ -261,27 +291,29 @@ def test_query_middlewares(): | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
| 
 | 
 | ||||||
|     executed = hello_schema.execute('{ hello, other }', middleware=[reversed_middleware]) |     executed = hello_schema.execute( | ||||||
|  |         '{ hello, other }', middleware=[reversed_middleware]) | ||||||
|     assert not executed.errors |     assert not executed.errors | ||||||
|     assert executed.data == {'hello': 'dlroW', 'other': 'rehto'} |     assert executed.data == {'hello': 'dlroW', 'other': 'rehto'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_objecttype_on_instances(): | def test_objecttype_on_instances(): | ||||||
|     class Ship: |     class Ship: | ||||||
|  | 
 | ||||||
|         def __init__(self, name): |         def __init__(self, name): | ||||||
|             self.name = name |             self.name = name | ||||||
| 
 | 
 | ||||||
|     class ShipType(ObjectType): |     class ShipType(ObjectType): | ||||||
|         name = String(description="Ship name", required=True) |         name = String(description="Ship name", required=True) | ||||||
| 
 | 
 | ||||||
|         def resolve_name(self, context, args, info): |         def resolve_name(self, info): | ||||||
|             # Here self will be the Ship instance returned in resolve_ship |             # Here self will be the Ship instance returned in resolve_ship | ||||||
|             return self.name |             return self.name | ||||||
| 
 | 
 | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         ship = Field(ShipType) |         ship = Field(ShipType) | ||||||
| 
 | 
 | ||||||
|         def resolve_ship(self, context, args, info): |         def resolve_ship(self, info): | ||||||
|             return Ship(name='xwing') |             return Ship(name='xwing') | ||||||
| 
 | 
 | ||||||
|     schema = Schema(query=Query) |     schema = Schema(query=Query) | ||||||
|  | @ -296,7 +328,7 @@ def test_big_list_query_benchmark(benchmark): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         all_ints = List(Int) |         all_ints = List(Int) | ||||||
| 
 | 
 | ||||||
|         def resolve_all_ints(self, args, context, info): |         def resolve_all_ints(self, info): | ||||||
|             return big_list |             return big_list | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
|  | @ -313,7 +345,7 @@ def test_big_list_query_compiled_query_benchmark(benchmark): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         all_ints = List(Int) |         all_ints = List(Int) | ||||||
| 
 | 
 | ||||||
|         def resolve_all_ints(self, args, context, info): |         def resolve_all_ints(self, info): | ||||||
|             return big_list |             return big_list | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
|  | @ -335,7 +367,7 @@ def test_big_list_of_containers_query_benchmark(benchmark): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         all_containers = List(Container) |         all_containers = List(Container) | ||||||
| 
 | 
 | ||||||
|         def resolve_all_containers(self, args, context, info): |         def resolve_all_containers(self, info): | ||||||
|             return big_container_list |             return big_container_list | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
|  | @ -343,7 +375,8 @@ def test_big_list_of_containers_query_benchmark(benchmark): | ||||||
|     big_list_query = partial(hello_schema.execute, '{ allContainers { x } }') |     big_list_query = partial(hello_schema.execute, '{ allContainers { x } }') | ||||||
|     result = benchmark(big_list_query) |     result = benchmark(big_list_query) | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == {'allContainers': [{'x': c.x} for c in big_container_list]} |     assert result.data == {'allContainers': [ | ||||||
|  |         {'x': c.x} for c in big_container_list]} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_big_list_of_containers_multiple_fields_query_benchmark(benchmark): | def test_big_list_of_containers_multiple_fields_query_benchmark(benchmark): | ||||||
|  | @ -358,15 +391,17 @@ def test_big_list_of_containers_multiple_fields_query_benchmark(benchmark): | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         all_containers = List(Container) |         all_containers = List(Container) | ||||||
| 
 | 
 | ||||||
|         def resolve_all_containers(self, args, context, info): |         def resolve_all_containers(self, info): | ||||||
|             return big_container_list |             return big_container_list | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
| 
 | 
 | ||||||
|     big_list_query = partial(hello_schema.execute, '{ allContainers { x, y, z, o } }') |     big_list_query = partial(hello_schema.execute, | ||||||
|  |                              '{ allContainers { x, y, z, o } }') | ||||||
|     result = benchmark(big_list_query) |     result = benchmark(big_list_query) | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == {'allContainers': [{'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]} |     assert result.data == {'allContainers': [ | ||||||
|  |         {'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark(benchmark): | def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark(benchmark): | ||||||
|  | @ -376,16 +411,16 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark | ||||||
|         z = Int() |         z = Int() | ||||||
|         o = Int() |         o = Int() | ||||||
| 
 | 
 | ||||||
|         def resolve_x(self, args, context, info): |         def resolve_x(self, info): | ||||||
|             return self.x |             return self.x | ||||||
| 
 | 
 | ||||||
|         def resolve_y(self, args, context, info): |         def resolve_y(self, info): | ||||||
|             return self.y |             return self.y | ||||||
| 
 | 
 | ||||||
|         def resolve_z(self, args, context, info): |         def resolve_z(self, info): | ||||||
|             return self.z |             return self.z | ||||||
| 
 | 
 | ||||||
|         def resolve_o(self, args, context, info): |         def resolve_o(self, info): | ||||||
|             return self.o |             return self.o | ||||||
| 
 | 
 | ||||||
|     big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(1000)] |     big_container_list = [Container(x=x, y=x, z=x, o=x) for x in range(1000)] | ||||||
|  | @ -393,12 +428,50 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark | ||||||
|     class Query(ObjectType): |     class Query(ObjectType): | ||||||
|         all_containers = List(Container) |         all_containers = List(Container) | ||||||
| 
 | 
 | ||||||
|         def resolve_all_containers(self, args, context, info): |         def resolve_all_containers(self, info): | ||||||
|             return big_container_list |             return big_container_list | ||||||
| 
 | 
 | ||||||
|     hello_schema = Schema(Query) |     hello_schema = Schema(Query) | ||||||
| 
 | 
 | ||||||
|     big_list_query = partial(hello_schema.execute, '{ allContainers { x, y, z, o } }') |     big_list_query = partial(hello_schema.execute, | ||||||
|  |                              '{ allContainers { x, y, z, o } }') | ||||||
|     result = benchmark(big_list_query) |     result = benchmark(big_list_query) | ||||||
|     assert not result.errors |     assert not result.errors | ||||||
|     assert result.data == {'allContainers': [{'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]} |     assert result.data == {'allContainers': [ | ||||||
|  |         {'x': c.x, 'y': c.y, 'z': c.z, 'o': c.o} for c in big_container_list]} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_query_annotated_resolvers(): | ||||||
|  |     import json | ||||||
|  | 
 | ||||||
|  |     context = Context(key="context") | ||||||
|  | 
 | ||||||
|  |     class Query(ObjectType): | ||||||
|  |         annotated = String(id=String()) | ||||||
|  |         context = String() | ||||||
|  |         info = String() | ||||||
|  | 
 | ||||||
|  |         def resolve_annotated(self, info, id): | ||||||
|  |             return "{}-{}".format(self, id) | ||||||
|  | 
 | ||||||
|  |         def resolve_context(self, info): | ||||||
|  |             assert isinstance(info.context, Context) | ||||||
|  |             return "{}-{}".format(self, info.context.key) | ||||||
|  | 
 | ||||||
|  |         def resolve_info(self, info): | ||||||
|  |             assert isinstance(info, ResolveInfo) | ||||||
|  |             return "{}-{}".format(self, info.field_name) | ||||||
|  | 
 | ||||||
|  |     test_schema = Schema(Query) | ||||||
|  | 
 | ||||||
|  |     result = test_schema.execute('{ annotated(id:"self") }', "base") | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data == {'annotated': 'base-self'} | ||||||
|  | 
 | ||||||
|  |     result = test_schema.execute('{ context }', "base", context_value=context) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data == {'context': 'base-context'} | ||||||
|  | 
 | ||||||
|  |     result = test_schema.execute('{ info }', "base") | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data == {'info': 'base-info'} | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import pytest |  | ||||||
| 
 | 
 | ||||||
| from ..resolver import attr_resolver, dict_resolver, get_default_resolver, set_default_resolver | from ..resolver import (attr_resolver, dict_resolver, get_default_resolver, | ||||||
|  |                         set_default_resolver) | ||||||
| 
 | 
 | ||||||
| args = {} | args = {} | ||||||
| context = None | context = None | ||||||
|  | @ -16,22 +16,22 @@ class demo_obj(object): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_attr_resolver(): | def test_attr_resolver(): | ||||||
|     resolved = attr_resolver('attr', None, demo_obj, args, context, info) |     resolved = attr_resolver('attr', None, demo_obj, info, **args) | ||||||
|     assert resolved == 'value' |     assert resolved == 'value' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_attr_resolver_default_value(): | def test_attr_resolver_default_value(): | ||||||
|     resolved = attr_resolver('attr2', 'default', demo_obj, args, context, info) |     resolved = attr_resolver('attr2', 'default', demo_obj, info, **args) | ||||||
|     assert resolved == 'default' |     assert resolved == 'default' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dict_resolver(): | def test_dict_resolver(): | ||||||
|     resolved = dict_resolver('attr', None, demo_dict, args, context, info) |     resolved = dict_resolver('attr', None, demo_dict, info, **args) | ||||||
|     assert resolved == 'value' |     assert resolved == 'value' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dict_resolver_default_value(): | def test_dict_resolver_default_value(): | ||||||
|     resolved = dict_resolver('attr2', 'default', demo_dict, args, context, info) |     resolved = dict_resolver('attr2', 'default', demo_dict, info, **args) | ||||||
|     assert resolved == 'default' |     assert resolved == 'default' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								graphene/types/tests/test_scalar.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								graphene/types/tests/test_scalar.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | 
 | ||||||
|  | from ..scalars import Scalar | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_scalar(): | ||||||
|  |     class JSONScalar(Scalar): | ||||||
|  |         '''Documentation''' | ||||||
|  | 
 | ||||||
|  |     assert JSONScalar._meta.name == "JSONScalar" | ||||||
|  |     assert JSONScalar._meta.description == "Documentation" | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
| from ..schema import Schema | from ..field import Field | ||||||
| from ..objecttype import ObjectType | from ..objecttype import ObjectType | ||||||
| from ..scalars import String | from ..scalars import String | ||||||
| from ..field import Field | from ..schema import Schema | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MyOtherType(ObjectType): | class MyOtherType(ObjectType): | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| import pytest |  | ||||||
| from functools import partial | from functools import partial | ||||||
| 
 | 
 | ||||||
| from ..structures import List, NonNull | import pytest | ||||||
|  | 
 | ||||||
| from ..scalars import String | from ..scalars import String | ||||||
|  | from ..structures import List, NonNull | ||||||
| from .utils import MyLazyType | from .utils import MyLazyType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -49,8 +49,8 @@ def test_objecttype(): | ||||||
|         foo = String(bar=String(description='Argument description', default_value='x'), description='Field description') |         foo = String(bar=String(description='Argument description', default_value='x'), description='Field description') | ||||||
|         bar = String(name='gizmo') |         bar = String(name='gizmo') | ||||||
| 
 | 
 | ||||||
|         def resolve_foo(self, args, info): |         def resolve_foo(self, bar): | ||||||
|             return args.get('bar') |             return bar | ||||||
| 
 | 
 | ||||||
|     typemap = TypeMap([MyObjectType]) |     typemap = TypeMap([MyObjectType]) | ||||||
|     assert 'MyObjectType' in typemap |     assert 'MyObjectType' in typemap | ||||||
|  | @ -65,7 +65,7 @@ def test_objecttype(): | ||||||
|     assert isinstance(foo_field, GraphQLField) |     assert isinstance(foo_field, GraphQLField) | ||||||
|     assert foo_field.description == 'Field description' |     assert foo_field.description == 'Field description' | ||||||
|     f = MyObjectType.resolve_foo |     f = MyObjectType.resolve_foo | ||||||
|     assert foo_field.resolver == getattr(f, '__func__', f) |     # assert foo_field.resolver == getattr(f, '__func__', f) | ||||||
|     assert foo_field.args == { |     assert foo_field.args == { | ||||||
|         'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar') |         'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar') | ||||||
|     } |     } | ||||||
|  | @ -135,9 +135,17 @@ def test_inputobject(): | ||||||
|     assert graphql_type.name == 'MyInputObjectType' |     assert graphql_type.name == 'MyInputObjectType' | ||||||
|     assert graphql_type.description == 'Description' |     assert graphql_type.description == 'Description' | ||||||
| 
 | 
 | ||||||
|  |     # Container | ||||||
|  |     container = graphql_type.create_container({'bar': 'oh!'}) | ||||||
|  |     assert isinstance(container, MyInputObjectType) | ||||||
|  |     assert 'bar' in container | ||||||
|  |     assert container.bar == 'oh!' | ||||||
|  |     assert 'foo_bar' not in container | ||||||
|  | 
 | ||||||
|     fields = graphql_type.fields |     fields = graphql_type.fields | ||||||
|     assert list(fields.keys()) == ['fooBar', 'gizmo', 'own'] |     assert list(fields.keys()) == ['fooBar', 'gizmo', 'own'] | ||||||
|     assert fields['own'].type == graphql_type |     own_field = fields['own'] | ||||||
|  |     assert own_field.type == graphql_type | ||||||
|     foo_field = fields['fooBar'] |     foo_field = fields['fooBar'] | ||||||
|     assert isinstance(foo_field, GraphQLInputObjectField) |     assert isinstance(foo_field, GraphQLInputObjectField) | ||||||
|     assert foo_field.description == 'Field description' |     assert foo_field.description == 'Field description' | ||||||
|  | @ -196,5 +204,5 @@ def test_objecttype_with_possible_types(): | ||||||
|     typemap = TypeMap([MyObjectType]) |     typemap = TypeMap([MyObjectType]) | ||||||
|     graphql_type = typemap['MyObjectType'] |     graphql_type = typemap['MyObjectType'] | ||||||
|     assert graphql_type.is_type_of |     assert graphql_type.is_type_of | ||||||
|     assert graphql_type.is_type_of({}, None, None) is True |     assert graphql_type.is_type_of({}, None) is True | ||||||
|     assert graphql_type.is_type_of(MyObjectType(), None, None) is False |     assert graphql_type.is_type_of(MyObjectType(), None) is False | ||||||
|  |  | ||||||
|  | @ -47,6 +47,7 @@ def test_generate_union_with_no_types(): | ||||||
| 
 | 
 | ||||||
| def test_union_can_be_mounted(): | def test_union_can_be_mounted(): | ||||||
|     class MyUnion(Union): |     class MyUnion(Union): | ||||||
|  | 
 | ||||||
|         class Meta: |         class Meta: | ||||||
|             types = (MyObjectType1, MyObjectType2) |             types = (MyObjectType1, MyObjectType2) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										34
									
								
								graphene/types/tests/test_uuid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								graphene/types/tests/test_uuid.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | from ..uuid import UUID | ||||||
|  | from ..objecttype import ObjectType | ||||||
|  | from ..schema import Schema | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Query(ObjectType): | ||||||
|  |     uuid = UUID(input=UUID()) | ||||||
|  | 
 | ||||||
|  |     def resolve_uuid(self, info, input): | ||||||
|  |         return input | ||||||
|  | 
 | ||||||
|  | schema = Schema(query=Query) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_uuidstring_query(): | ||||||
|  |     uuid_value = 'dfeb3bcf-70fd-11e7-a61a-6003088f8204' | ||||||
|  |     result = schema.execute('''{ uuid(input: "%s") }''' % uuid_value) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data == { | ||||||
|  |         'uuid': uuid_value | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_uuidstring_query_variable(): | ||||||
|  |     uuid_value = 'dfeb3bcf-70fd-11e7-a61a-6003088f8204' | ||||||
|  | 
 | ||||||
|  |     result = schema.execute( | ||||||
|  |         '''query Test($uuid: UUID){ uuid(input: $uuid) }''', | ||||||
|  |         variable_values={'uuid': uuid_value} | ||||||
|  |     ) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data == { | ||||||
|  |         'uuid': uuid_value | ||||||
|  |     } | ||||||
|  | @ -11,10 +11,10 @@ from graphql.type.typemap import GraphQLTypeMap | ||||||
| 
 | 
 | ||||||
| from ..utils.get_unbound_function import get_unbound_function | from ..utils.get_unbound_function import get_unbound_function | ||||||
| from ..utils.str_converters import to_camel_case | from ..utils.str_converters import to_camel_case | ||||||
| from .definitions import (GrapheneEnumType, GrapheneInputObjectType, | from .definitions import (GrapheneEnumType, GrapheneGraphQLType, | ||||||
|                           GrapheneInterfaceType, GrapheneObjectType, |                           GrapheneInputObjectType, GrapheneInterfaceType, | ||||||
|                           GrapheneScalarType, GrapheneUnionType, |                           GrapheneObjectType, GrapheneScalarType, | ||||||
|                           GrapheneGraphQLType) |                           GrapheneUnionType) | ||||||
| from .dynamic import Dynamic | from .dynamic import Dynamic | ||||||
| from .enum import Enum | from .enum import Enum | ||||||
| from .field import Field | from .field import Field | ||||||
|  | @ -37,12 +37,12 @@ def is_graphene_type(_type): | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def resolve_type(resolve_type_func, map, type_name, root, context, info): | def resolve_type(resolve_type_func, map, type_name, root, info): | ||||||
|     _type = resolve_type_func(root, context, info) |     _type = resolve_type_func(root, info) | ||||||
| 
 | 
 | ||||||
|     if not _type: |     if not _type: | ||||||
|         return_type = map[type_name] |         return_type = map[type_name] | ||||||
|         return get_default_resolve_type_fn(root, context, info, return_type) |         return get_default_resolve_type_fn(root, info, return_type) | ||||||
| 
 | 
 | ||||||
|     if inspect.isclass(_type) and issubclass(_type, ObjectType): |     if inspect.isclass(_type) and issubclass(_type, ObjectType): | ||||||
|         graphql_type = map.get(_type._meta.name) |         graphql_type = map.get(_type._meta.name) | ||||||
|  | @ -54,11 +54,12 @@ def resolve_type(resolve_type_func, map, type_name, root, context, info): | ||||||
|     return _type |     return _type | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def is_type_of_from_possible_types(possible_types, root, context, info): | def is_type_of_from_possible_types(possible_types, root, info): | ||||||
|     return isinstance(root, possible_types) |     return isinstance(root, possible_types) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TypeMap(GraphQLTypeMap): | class TypeMap(GraphQLTypeMap): | ||||||
|  | 
 | ||||||
|     def __init__(self, types, auto_camelcase=True, schema=None): |     def __init__(self, types, auto_camelcase=True, schema=None): | ||||||
|         self.auto_camelcase = auto_camelcase |         self.auto_camelcase = auto_camelcase | ||||||
|         self.schema = schema |         self.schema = schema | ||||||
|  | @ -194,6 +195,7 @@ class TypeMap(GraphQLTypeMap): | ||||||
|             graphene_type=type, |             graphene_type=type, | ||||||
|             name=type._meta.name, |             name=type._meta.name, | ||||||
|             description=type._meta.description, |             description=type._meta.description, | ||||||
|  |             container_type=type._meta.container, | ||||||
|             fields=partial( |             fields=partial( | ||||||
|                 self.construct_fields_for_type, map, type, is_input_type=True), |                 self.construct_fields_for_type, map, type, is_input_type=True), | ||||||
|         ) |         ) | ||||||
|  | @ -237,7 +239,7 @@ class TypeMap(GraphQLTypeMap): | ||||||
|                 _field = GraphQLInputObjectField( |                 _field = GraphQLInputObjectField( | ||||||
|                     field_type, |                     field_type, | ||||||
|                     default_value=field.default_value, |                     default_value=field.default_value, | ||||||
|                     out_name=field.name or name, |                     out_name=name, | ||||||
|                     description=field.description) |                     description=field.description) | ||||||
|             else: |             else: | ||||||
|                 args = OrderedDict() |                 args = OrderedDict() | ||||||
|  | @ -254,8 +256,12 @@ class TypeMap(GraphQLTypeMap): | ||||||
|                     field_type, |                     field_type, | ||||||
|                     args=args, |                     args=args, | ||||||
|                     resolver=field.get_resolver( |                     resolver=field.get_resolver( | ||||||
|                         self.get_resolver_for_type(type, name, |                         self.get_resolver_for_type( | ||||||
|                                                    field.default_value)), |                             type, | ||||||
|  |                             name, | ||||||
|  |                             field.default_value | ||||||
|  |                         ) | ||||||
|  |                     ), | ||||||
|                     deprecation_reason=field.deprecation_reason, |                     deprecation_reason=field.deprecation_reason, | ||||||
|                     description=field.description) |                     description=field.description) | ||||||
|             field_name = field.name or self.get_name(name) |             field_name = field.name or self.get_name(name) | ||||||
|  |  | ||||||
|  | @ -1,38 +1,19 @@ | ||||||
| import six | from .base import BaseOptions, BaseType | ||||||
| 
 |  | ||||||
| from ..utils.is_base_type import is_base_type |  | ||||||
| from ..utils.trim_docstring import trim_docstring |  | ||||||
| from .options import Options |  | ||||||
| from .unmountedtype import UnmountedType | from .unmountedtype import UnmountedType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class UnionMeta(type): | # For static type checking with Mypy | ||||||
| 
 | MYPY = False | ||||||
|     def __new__(cls, name, bases, attrs): | if MYPY: | ||||||
|         # Also ensure initialization is only performed for subclasses of |     from .objecttype import ObjectType  # NOQA | ||||||
|         # Union |     from typing import Iterable, Type  # NOQA | ||||||
|         if not is_base_type(bases, UnionMeta): |  | ||||||
|             return type.__new__(cls, name, bases, attrs) |  | ||||||
| 
 |  | ||||||
|         options = Options( |  | ||||||
|             attrs.pop('Meta', None), |  | ||||||
|             name=name, |  | ||||||
|             description=trim_docstring(attrs.get('__doc__')), |  | ||||||
|             types=(), |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         assert ( |  | ||||||
|             isinstance(options.types, (list, tuple)) and |  | ||||||
|             len(options.types) > 0 |  | ||||||
|         ), 'Must provide types for Union {}.'.format(options.name) |  | ||||||
| 
 |  | ||||||
|         return type.__new__(cls, name, bases, dict(attrs, _meta=options)) |  | ||||||
| 
 |  | ||||||
|     def __str__(cls):  # noqa: N805 |  | ||||||
|         return cls._meta.name |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Union(six.with_metaclass(UnionMeta, UnmountedType)): | class UnionOptions(BaseOptions): | ||||||
|  |     types = ()  # type: Iterable[Type[ObjectType]] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Union(UnmountedType, BaseType): | ||||||
|     ''' |     ''' | ||||||
|     Union Type Definition |     Union Type Definition | ||||||
| 
 | 
 | ||||||
|  | @ -40,6 +21,16 @@ class Union(six.with_metaclass(UnionMeta, UnmountedType)): | ||||||
|     is used to describe what types are possible as well as providing a function |     is used to describe what types are possible as well as providing a function | ||||||
|     to determine which type is actually used when the field is resolved. |     to determine which type is actually used when the field is resolved. | ||||||
|     ''' |     ''' | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, types=None, **options): | ||||||
|  |         assert ( | ||||||
|  |             isinstance(types, (list, tuple)) and | ||||||
|  |             len(types) > 0 | ||||||
|  |         ), 'Must provide types for Union {name}.'.format(name=cls.__name__) | ||||||
|  | 
 | ||||||
|  |         _meta = UnionOptions(cls) | ||||||
|  |         _meta.types = types | ||||||
|  |         super(Union, cls).__init_subclass_with_meta__(_meta=_meta, **options) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_type(cls): |     def get_type(cls): | ||||||
|  | @ -50,7 +41,7 @@ class Union(six.with_metaclass(UnionMeta, UnmountedType)): | ||||||
|         return cls |         return cls | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def resolve_type(cls, instance, context, info): |     def resolve_type(cls, instance, info): | ||||||
|         from .objecttype import ObjectType |         from .objecttype import ObjectType  # NOQA | ||||||
|         if isinstance(instance, ObjectType): |         if isinstance(instance, ObjectType): | ||||||
|             return type(instance) |             return type(instance) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import inspect | import inspect | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
| from functools import partial | from functools import partial | ||||||
|  | 
 | ||||||
| from six import string_types | from six import string_types | ||||||
| 
 | 
 | ||||||
| from ..utils.module_loading import import_string | from ..utils.module_loading import import_string | ||||||
|  | @ -8,35 +9,6 @@ from .mountedtype import MountedType | ||||||
| from .unmountedtype import UnmountedType | from .unmountedtype import UnmountedType | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def merge(*dicts): |  | ||||||
|     ''' |  | ||||||
|     Merge the dicts into one |  | ||||||
|     ''' |  | ||||||
|     merged = OrderedDict() |  | ||||||
|     for _dict in dicts: |  | ||||||
|         merged.update(_dict) |  | ||||||
|     return merged |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_base_fields(bases, _as=None): |  | ||||||
|     ''' |  | ||||||
|     Get all the fields in the given bases |  | ||||||
|     ''' |  | ||||||
|     fields = OrderedDict() |  | ||||||
|     from ..types import AbstractType, Interface |  | ||||||
|     # We allow inheritance in AbstractTypes and Interfaces but not ObjectTypes |  | ||||||
|     inherited_bases = (AbstractType, Interface) |  | ||||||
|     for base in bases: |  | ||||||
|         if base in inherited_bases or not issubclass(base, inherited_bases): |  | ||||||
|             continue |  | ||||||
|         for name, field in base._meta.fields.items(): |  | ||||||
|             if name in fields: |  | ||||||
|                 continue |  | ||||||
|             fields[name] = get_field_as(field, _as=_as) |  | ||||||
| 
 |  | ||||||
|     return fields |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_field_as(value, _as=None): | def get_field_as(value, _as=None): | ||||||
|     ''' |     ''' | ||||||
|     Get type mounted |     Get type mounted | ||||||
|  | @ -49,7 +21,7 @@ def get_field_as(value, _as=None): | ||||||
|         return _as.mounted(value) |         return _as.mounted(value) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True): | def yank_fields_from_attrs(attrs, _as=None, sort=True): | ||||||
|     ''' |     ''' | ||||||
|     Extract all the fields in given attributes (dict) |     Extract all the fields in given attributes (dict) | ||||||
|     and return them ordered |     and return them ordered | ||||||
|  | @ -60,8 +32,6 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True): | ||||||
|         if not field: |         if not field: | ||||||
|             continue |             continue | ||||||
|         fields_with_names.append((attname, field)) |         fields_with_names.append((attname, field)) | ||||||
|         if delete: |  | ||||||
|             del attrs[attname] |  | ||||||
| 
 | 
 | ||||||
|     if sort: |     if sort: | ||||||
|         fields_with_names = sorted(fields_with_names, key=lambda f: f[1]) |         fields_with_names = sorted(fields_with_names, key=lambda f: f[1]) | ||||||
|  | @ -71,6 +41,6 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True): | ||||||
| def get_type(_type): | def get_type(_type): | ||||||
|     if isinstance(_type, string_types): |     if isinstance(_type, string_types): | ||||||
|         return import_string(_type) |         return import_string(_type) | ||||||
|     if inspect.isfunction(_type) or type(_type) is partial: |     if inspect.isfunction(_type) or isinstance(_type, partial): | ||||||
|         return _type() |         return _type() | ||||||
|     return _type |     return _type | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								graphene/types/uuid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								graphene/types/uuid.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | from __future__ import absolute_import | ||||||
|  | 
 | ||||||
|  | from uuid import UUID as _UUID | ||||||
|  | 
 | ||||||
|  | from graphql.language import ast | ||||||
|  | 
 | ||||||
|  | from .scalars import Scalar | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class UUID(Scalar): | ||||||
|  |     '''UUID''' | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def serialize(uuid): | ||||||
|  |         if isinstance(uuid, str): | ||||||
|  |             uuid = _UUID(uuid) | ||||||
|  |         assert isinstance(uuid, _UUID), "Expected UUID instance, received {}".format(uuid) | ||||||
|  |         return str(uuid) | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def parse_literal(node): | ||||||
|  |         if isinstance(node, ast.StringValue): | ||||||
|  |             return _UUID(node.value) | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def parse_value(value): | ||||||
|  |         return _UUID(value) | ||||||
							
								
								
									
										36
									
								
								graphene/utils/annotate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								graphene/utils/annotate.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | import six | ||||||
|  | from ..pyutils.compat import signature, func_name | ||||||
|  | 
 | ||||||
|  | from .deprecated import warn_deprecation | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def annotate(_func=None, _trigger_warning=True, **annotations): | ||||||
|  |     if not six.PY2 and _trigger_warning: | ||||||
|  |         warn_deprecation( | ||||||
|  |             "annotate is intended for use in Python 2 only, as you can use type annotations Python 3.\n" | ||||||
|  |             "Read more in https://docs.python.org/3/library/typing.html" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     if not _func: | ||||||
|  |         def _func(f): | ||||||
|  |             return annotate(f, **annotations) | ||||||
|  |         return _func | ||||||
|  | 
 | ||||||
|  |     func_signature = signature(_func) | ||||||
|  | 
 | ||||||
|  |     # We make sure the annotations are valid | ||||||
|  |     for key, value in annotations.items(): | ||||||
|  |         assert key in func_signature.parameters, ( | ||||||
|  |             'The key {key} is not a function parameter in the function "{func_name}".' | ||||||
|  |         ).format( | ||||||
|  |             key=key, | ||||||
|  |             func_name=func_name(_func) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     func_annotations = getattr(_func, '__annotations__', None) | ||||||
|  |     if func_annotations is None: | ||||||
|  |         _func.__annotations__ = annotations | ||||||
|  |     else: | ||||||
|  |         _func.__annotations__.update(annotations) | ||||||
|  | 
 | ||||||
|  |     return _func | ||||||
							
								
								
									
										80
									
								
								graphene/utils/deprecated.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								graphene/utils/deprecated.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | ||||||
|  | import functools | ||||||
|  | import inspect | ||||||
|  | import warnings | ||||||
|  | 
 | ||||||
|  | string_types = (type(b''), type(u'')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def warn_deprecation(text): | ||||||
|  |     warnings.simplefilter('always', DeprecationWarning) | ||||||
|  |     warnings.warn( | ||||||
|  |         text, | ||||||
|  |         category=DeprecationWarning, | ||||||
|  |         stacklevel=2 | ||||||
|  |     ) | ||||||
|  |     warnings.simplefilter('default', DeprecationWarning) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def deprecated(reason): | ||||||
|  |     """ | ||||||
|  |     This is a decorator which can be used to mark functions | ||||||
|  |     as deprecated. It will result in a warning being emitted | ||||||
|  |     when the function is used. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     if isinstance(reason, string_types): | ||||||
|  | 
 | ||||||
|  |         # The @deprecated is used with a 'reason'. | ||||||
|  |         # | ||||||
|  |         # .. code-block:: python | ||||||
|  |         # | ||||||
|  |         #    @deprecated("please, use another function") | ||||||
|  |         #    def old_function(x, y): | ||||||
|  |         #      pass | ||||||
|  | 
 | ||||||
|  |         def decorator(func1): | ||||||
|  | 
 | ||||||
|  |             if inspect.isclass(func1): | ||||||
|  |                 fmt1 = "Call to deprecated class {name} ({reason})." | ||||||
|  |             else: | ||||||
|  |                 fmt1 = "Call to deprecated function {name} ({reason})." | ||||||
|  | 
 | ||||||
|  |             @functools.wraps(func1) | ||||||
|  |             def new_func1(*args, **kwargs): | ||||||
|  |                 warn_deprecation( | ||||||
|  |                     fmt1.format(name=func1.__name__, reason=reason), | ||||||
|  |                 ) | ||||||
|  |                 return func1(*args, **kwargs) | ||||||
|  | 
 | ||||||
|  |             return new_func1 | ||||||
|  | 
 | ||||||
|  |         return decorator | ||||||
|  | 
 | ||||||
|  |     elif inspect.isclass(reason) or inspect.isfunction(reason): | ||||||
|  | 
 | ||||||
|  |         # The @deprecated is used without any 'reason'. | ||||||
|  |         # | ||||||
|  |         # .. code-block:: python | ||||||
|  |         # | ||||||
|  |         #    @deprecated | ||||||
|  |         #    def old_function(x, y): | ||||||
|  |         #      pass | ||||||
|  | 
 | ||||||
|  |         func2 = reason | ||||||
|  | 
 | ||||||
|  |         if inspect.isclass(func2): | ||||||
|  |             fmt2 = "Call to deprecated class {name}." | ||||||
|  |         else: | ||||||
|  |             fmt2 = "Call to deprecated function {name}." | ||||||
|  | 
 | ||||||
|  |         @functools.wraps(func2) | ||||||
|  |         def new_func2(*args, **kwargs): | ||||||
|  |             warn_deprecation( | ||||||
|  |                 fmt2.format(name=func2.__name__), | ||||||
|  |             ) | ||||||
|  |             return func2(*args, **kwargs) | ||||||
|  | 
 | ||||||
|  |         return new_func2 | ||||||
|  | 
 | ||||||
|  |     else: | ||||||
|  |         raise TypeError(repr(type(reason))) | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| 
 |  | ||||||
| def is_base_type(bases, _type): |  | ||||||
|     return any(b for b in bases if isinstance(b, _type)) |  | ||||||
|  | @ -1,8 +1,11 @@ | ||||||
| from functools import wraps | from functools import wraps | ||||||
|  | from .deprecated import deprecated | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @deprecated('This function is deprecated') | ||||||
| def resolve_only_args(func): | def resolve_only_args(func): | ||||||
|     @wraps(func) |     @wraps(func) | ||||||
|     def inner(root, args, context, info): |     def wrapped_func(root, info, **args): | ||||||
|         return func(root, **args) |         return func(root, **args) | ||||||
|     return inner | 
 | ||||||
|  |     return wrapped_func | ||||||
|  |  | ||||||
							
								
								
									
										44
									
								
								graphene/utils/subclass_with_meta.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								graphene/utils/subclass_with_meta.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | ||||||
|  | import six | ||||||
|  | from inspect import isclass | ||||||
|  | 
 | ||||||
|  | from ..pyutils.init_subclass import InitSubclassMeta | ||||||
|  | from .props import props | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SubclassWithMeta_Meta(InitSubclassMeta): | ||||||
|  | 
 | ||||||
|  |     def __repr__(cls): | ||||||
|  |         return cls._meta.name | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_Meta)): | ||||||
|  |     """This class improves __init_subclass__ to receive automatically the options from meta""" | ||||||
|  |     # We will only have the metaclass in Python 2 | ||||||
|  |     def __init_subclass__(cls, **meta_options): | ||||||
|  |         """This method just terminates the super() chain""" | ||||||
|  |         _Meta = getattr(cls, "Meta", None) | ||||||
|  |         _meta_props = {} | ||||||
|  |         if _Meta: | ||||||
|  |             if isinstance(_Meta, dict): | ||||||
|  |                 _meta_props = _Meta | ||||||
|  |             elif isclass(_Meta): | ||||||
|  |                 _meta_props = props(_Meta) | ||||||
|  |             else: | ||||||
|  |                 raise Exception("Meta have to be either a class or a dict. Received {}".format(_Meta)) | ||||||
|  |             delattr(cls, "Meta") | ||||||
|  |         options = dict(meta_options, **_meta_props) | ||||||
|  | 
 | ||||||
|  |         abstract = options.pop('abstract', False) | ||||||
|  |         if abstract: | ||||||
|  |             assert not options, ( | ||||||
|  |                 "Abstract types can only contain the abstract attribute. " | ||||||
|  |                 "Received: abstract, {option_keys}" | ||||||
|  |             ).format(option_keys=', '.join(options.keys())) | ||||||
|  |         else: | ||||||
|  |             super_class = super(cls, cls) | ||||||
|  |             if hasattr(super_class, '__init_subclass_with_meta__'): | ||||||
|  |                 super_class.__init_subclass_with_meta__(**options) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def __init_subclass_with_meta__(cls, **meta_options): | ||||||
|  |         """This method just terminates the super() chain""" | ||||||
							
								
								
									
										33
									
								
								graphene/utils/tests/test_annotate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								graphene/utils/tests/test_annotate.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | import pytest | ||||||
|  | from ..annotate import annotate | ||||||
|  | 
 | ||||||
|  | def func(a, b, *c, **d): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | annotations = { | ||||||
|  |     'a': int, | ||||||
|  |     'b': str, | ||||||
|  |     'c': list, | ||||||
|  |     'd': dict | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | def func_with_annotations(a, b, *c, **d): | ||||||
|  |     pass | ||||||
|  | func_with_annotations.__annotations__ = annotations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_annotate_with_no_params(): | ||||||
|  |     annotated_func = annotate(func, _trigger_warning=False) | ||||||
|  |     assert annotated_func.__annotations__ == {} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_annotate_with_params(): | ||||||
|  |     annotated_func = annotate(_trigger_warning=False, **annotations)(func) | ||||||
|  |     assert annotated_func.__annotations__ == annotations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_annotate_with_wront_params(): | ||||||
|  |     with pytest.raises(Exception) as exc_info: | ||||||
|  |         annotated_func = annotate(p=int, _trigger_warning=False)(func) | ||||||
|  | 
 | ||||||
|  |     assert str(exc_info.value) == 'The key p is not a function parameter in the function "func".' | ||||||
							
								
								
									
										65
									
								
								graphene/utils/tests/test_deprecated.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								graphene/utils/tests/test_deprecated.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | import pytest | ||||||
|  | from .. import deprecated | ||||||
|  | from ..deprecated import deprecated as deprecated_decorator, warn_deprecation | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_warn_deprecation(mocker): | ||||||
|  |     mocker.patch.object(deprecated.warnings, 'warn') | ||||||
|  |      | ||||||
|  |     warn_deprecation("OH!") | ||||||
|  |     deprecated.warnings.warn.assert_called_with('OH!', stacklevel=2, category=DeprecationWarning) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_deprecated_decorator(mocker): | ||||||
|  |     mocker.patch.object(deprecated, 'warn_deprecation') | ||||||
|  |      | ||||||
|  |     @deprecated_decorator | ||||||
|  |     def my_func(): | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     result = my_func() | ||||||
|  |     assert result | ||||||
|  |     deprecated.warn_deprecation.assert_called_with("Call to deprecated function my_func.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_deprecated_class(mocker): | ||||||
|  |     mocker.patch.object(deprecated, 'warn_deprecation') | ||||||
|  |      | ||||||
|  |     @deprecated_decorator | ||||||
|  |     class X: | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     result = X() | ||||||
|  |     assert result | ||||||
|  |     deprecated.warn_deprecation.assert_called_with("Call to deprecated class X.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_deprecated_decorator_text(mocker): | ||||||
|  |     mocker.patch.object(deprecated, 'warn_deprecation') | ||||||
|  |      | ||||||
|  |     @deprecated_decorator("Deprecation text") | ||||||
|  |     def my_func(): | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     result = my_func() | ||||||
|  |     assert result | ||||||
|  |     deprecated.warn_deprecation.assert_called_with("Call to deprecated function my_func (Deprecation text).") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_deprecated_class_text(mocker): | ||||||
|  |     mocker.patch.object(deprecated, 'warn_deprecation') | ||||||
|  |      | ||||||
|  |     @deprecated_decorator("Deprecation text") | ||||||
|  |     class X: | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     result = X() | ||||||
|  |     assert result | ||||||
|  |     deprecated.warn_deprecation.assert_called_with("Call to deprecated class X (Deprecation text).") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_deprecated_other_object(mocker): | ||||||
|  |     mocker.patch.object(deprecated, 'warn_deprecation') | ||||||
|  |      | ||||||
|  |     with pytest.raises(TypeError) as exc_info: | ||||||
|  |         deprecated_decorator({}) | ||||||
|  | @ -1,16 +1,16 @@ | ||||||
| from pytest import raises | from pytest import raises | ||||||
| 
 | 
 | ||||||
| from graphene import String | from graphene import ObjectType, String | ||||||
| from graphene.types.objecttype import ObjectTypeMeta | 
 | ||||||
| from ..module_loading import lazy_import, import_string | from ..module_loading import import_string, lazy_import | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_import_string(): | def test_import_string(): | ||||||
|     MyString = import_string('graphene.String') |     MyString = import_string('graphene.String') | ||||||
|     assert MyString == String |     assert MyString == String | ||||||
| 
 | 
 | ||||||
|     MyObjectTypeMeta = import_string('graphene.ObjectType', '__class__') |     MyObjectTypeMeta = import_string('graphene.ObjectType', '__doc__') | ||||||
|     assert MyObjectTypeMeta == ObjectTypeMeta |     assert MyObjectTypeMeta == ObjectType.__doc__ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_import_string_module(): | def test_import_string_module(): | ||||||
|  | @ -52,6 +52,6 @@ def test_lazy_import(): | ||||||
|     MyString = f() |     MyString = f() | ||||||
|     assert MyString == String |     assert MyString == String | ||||||
| 
 | 
 | ||||||
|     f = lazy_import('graphene.ObjectType', '__class__') |     f = lazy_import('graphene.ObjectType', '__doc__') | ||||||
|     MyObjectTypeMeta = f() |     MyObjectTypeMeta = f() | ||||||
|     assert MyObjectTypeMeta == ObjectTypeMeta |     assert MyObjectTypeMeta == ObjectType.__doc__ | ||||||
|  |  | ||||||
|  | @ -1,12 +1,15 @@ | ||||||
| from ..resolve_only_args import resolve_only_args | from ..resolve_only_args import resolve_only_args | ||||||
|  | from .. import deprecated | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_resolve_only_args(): | def test_resolve_only_args(mocker): | ||||||
| 
 |     mocker.patch.object(deprecated, 'warn_deprecation') | ||||||
|     def resolver(*args, **kwargs): |     def resolver(root, **args): | ||||||
|         return kwargs |         return root, args | ||||||
| 
 | 
 | ||||||
|     my_data = {'one': 1, 'two': 2} |     my_data = {'one': 1, 'two': 2} | ||||||
| 
 | 
 | ||||||
|     wrapped = resolve_only_args(resolver) |     wrapped_resolver = resolve_only_args(resolver) | ||||||
|     assert wrapped(None, my_data, None, None) == my_data |     assert deprecated.warn_deprecation.called | ||||||
|  |     result = wrapped_resolver(1, 2, a=3) | ||||||
|  |     assert result == (1, {'a': 3}) | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ def test_trim_docstring(): | ||||||
| 
 | 
 | ||||||
|         Multiple paragraphs too |         Multiple paragraphs too | ||||||
|         """ |         """ | ||||||
|         pass |  | ||||||
| 
 | 
 | ||||||
|     assert (trim_docstring(WellDocumentedObject.__doc__) == |     assert (trim_docstring(WellDocumentedObject.__doc__) == | ||||||
|             "This object is very well-documented. It has multiple lines in its\n" |             "This object is very well-documented. It has multiple lines in its\n" | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user