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