|
1 | 1 | import logging
|
| 2 | +from abc import ABC |
2 | 3 | from typing import Dict, Iterable, List, Optional, Tuple, Union
|
3 | 4 |
|
4 | 5 | from graphql import (
|
|
26 | 27 |
|
27 | 28 |
|
28 | 29 | def dsl_gql(
|
29 |
| - *fields: "DSLField", |
30 |
| - operation_name: Optional[str] = None, |
31 |
| - **fields_with_alias: "DSLField", |
| 30 | + *operations: "DSLOperation", **operations_with_name: "DSLOperation" |
32 | 31 | ) -> DocumentNode:
|
33 |
| - r"""Given arguments of type :class:`DSLField` containing GraphQL requests, |
| 32 | + r"""Given arguments instances of :class:`DSLOperation` |
| 33 | + containing GraphQL operations, |
34 | 34 | generate a Document which can be executed later in a
|
35 | 35 | gql client or a gql session.
|
36 | 36 |
|
37 | 37 | Similar to the :func:`gql.gql` function but instead of parsing a python
|
38 |
| - string to describe the request, we are using requests which have been generated |
| 38 | + string to describe the request, we are using operations which have been generated |
39 | 39 | dynamically using instances of :class:`DSLField`, generated
|
40 | 40 | by instances of :class:`DSLType` which themselves originated from
|
41 | 41 | a :class:`DSLSchema` class.
|
42 | 42 |
|
43 |
| - The fields arguments should be fields of root GraphQL types |
44 |
| - (Query, Mutation or Subscription). |
| 43 | + :param \*operations: the GraphQL operations |
| 44 | + :type \*operations: DSLOperation (DSLQuery, DSLMutation, DSLSubscription) |
| 45 | + :param \**operations_with_name: the GraphQL operations with an operation name |
| 46 | + :type \**operations_with_name: DSLOperation (DSLQuery, DSLMutation, DSLSubscription) |
45 | 47 |
|
46 |
| - They should all have the same root type |
47 |
| - (you can't mix queries with mutations for example). |
48 |
| -
|
49 |
| - :param \*fields: root instances of the dynamically generated requests |
50 |
| - :type \*fields: DSLField |
51 |
| - :param \**fields_with_alias: root instances fields with alias as key |
52 |
| - :type \**fields_with_alias: DSLField |
53 |
| - :param operation_name: optional operation name |
54 |
| - :type operation_name: str |
55 | 48 | :return: a Document which can be later executed or subscribed by a
|
56 | 49 | :class:`Client <gql.client.Client>`, by an
|
57 | 50 | :class:`async session <gql.client.AsyncClientSession>` or by a
|
58 | 51 | :class:`sync session <gql.client.SyncClientSession>`
|
59 | 52 |
|
60 |
| - :raises TypeError: if an argument is not an instance of :class:`DSLField` |
61 |
| - :raises AssertionError: if an argument is not a field of a root type |
| 53 | + :raises TypeError: if an argument is not an instance of :class:`DSLOperation` |
62 | 54 | """
|
63 | 55 |
|
64 |
| - all_fields: Tuple["DSLField", ...] = DSLField.get_aliased_fields( |
65 |
| - fields, fields_with_alias |
| 56 | + # Concatenate operations without and with name |
| 57 | + all_operations: Tuple["DSLOperation", ...] = ( |
| 58 | + *operations, |
| 59 | + *(operation for operation in operations_with_name.values()), |
66 | 60 | )
|
67 | 61 |
|
68 |
| - # Check that we receive only arguments of type DSLField |
69 |
| - # And that they are a root type |
70 |
| - for field in all_fields: |
71 |
| - if not isinstance(field, DSLField): |
| 62 | + # Set the operation name |
| 63 | + for name, operation in operations_with_name.items(): |
| 64 | + operation.name = name |
| 65 | + |
| 66 | + # Check the type |
| 67 | + for operation in all_operations: |
| 68 | + if not isinstance(operation, DSLOperation): |
72 | 69 | raise TypeError(
|
73 |
| - f"fields must be instances of DSLField. Received type: {type(field)}" |
| 70 | + "Operations should be instances of DSLOperation " |
| 71 | + "(DSLQuery, DSLMutation or DSLSubscription).\n" |
| 72 | + f"Received: {type(operation)}." |
74 | 73 | )
|
75 |
| - assert field.type_name in ["Query", "Mutation", "Subscription"], ( |
76 |
| - "fields should be root types (Query, Mutation or Subscription)\n" |
77 |
| - f"Received: {field.type_name}" |
78 |
| - ) |
79 |
| - |
80 |
| - # Get the operation from the first field |
81 |
| - # All the fields must have the same operation |
82 |
| - operation = all_fields[0].type_name.lower() |
83 | 74 |
|
84 | 75 | return DocumentNode(
|
85 | 76 | definitions=[
|
86 | 77 | OperationDefinitionNode(
|
87 |
| - operation=OperationType(operation), |
88 |
| - selection_set=SelectionSetNode( |
89 |
| - selections=FrozenList(DSLField.get_ast_fields(all_fields)) |
90 |
| - ), |
91 |
| - **({"name": NameNode(value=operation_name)} if operation_name else {}), |
| 78 | + operation=OperationType(operation.operation_type), |
| 79 | + selection_set=operation.selection_set, |
| 80 | + **({"name": NameNode(value=operation.name)} if operation.name else {}), |
92 | 81 | )
|
| 82 | + for operation in all_operations |
93 | 83 | ]
|
94 | 84 | )
|
95 | 85 |
|
@@ -133,6 +123,77 @@ def __getattr__(self, name: str) -> "DSLType":
|
133 | 123 | return DSLType(type_def)
|
134 | 124 |
|
135 | 125 |
|
| 126 | +class DSLOperation(ABC): |
| 127 | + """Interface for GraphQL operations. |
| 128 | +
|
| 129 | + Inherited by |
| 130 | + :class:`DSLQuery <gql.dsl.DSLQuery>`, |
| 131 | + :class:`DSLMutation <gql.dsl.DSLMutation>` and |
| 132 | + :class:`DSLSubscription <gql.dsl.DSLSubscription>` |
| 133 | + """ |
| 134 | + |
| 135 | + operation_type: OperationType |
| 136 | + |
| 137 | + def __init__( |
| 138 | + self, *fields: "DSLField", **fields_with_alias: "DSLField", |
| 139 | + ): |
| 140 | + r"""Given arguments of type :class:`DSLField` containing GraphQL requests, |
| 141 | + generate an operation which can be converted to a Document |
| 142 | + using the :func:`dsl_gql <gql.dsl.dsl_gql>`. |
| 143 | +
|
| 144 | + The fields arguments should be fields of root GraphQL types |
| 145 | + (Query, Mutation or Subscription) and correspond to the |
| 146 | + operation_type of this operation. |
| 147 | +
|
| 148 | + :param \*fields: root instances of the dynamically generated requests |
| 149 | + :type \*fields: DSLField |
| 150 | + :param \**fields_with_alias: root instances fields with alias as key |
| 151 | + :type \**fields_with_alias: DSLField |
| 152 | +
|
| 153 | + :raises TypeError: if an argument is not an instance of :class:`DSLField` |
| 154 | + :raises AssertionError: if an argument is not a field which correspond |
| 155 | + to the operation type |
| 156 | + """ |
| 157 | + |
| 158 | + self.name: Optional[str] = None |
| 159 | + |
| 160 | + # Concatenate fields without and with alias |
| 161 | + all_fields: Tuple["DSLField", ...] = DSLField.get_aliased_fields( |
| 162 | + fields, fields_with_alias |
| 163 | + ) |
| 164 | + |
| 165 | + # Check that we receive only arguments of type DSLField |
| 166 | + # And that the root type correspond to the operation |
| 167 | + for field in all_fields: |
| 168 | + if not isinstance(field, DSLField): |
| 169 | + raise TypeError( |
| 170 | + ( |
| 171 | + "fields must be instances of DSLField. " |
| 172 | + f"Received type: {type(field)}" |
| 173 | + ) |
| 174 | + ) |
| 175 | + assert field.type_name.upper() == self.operation_type.name, ( |
| 176 | + f"Invalid root field for operation {self.operation_type.name}.\n" |
| 177 | + f"Received: {field.type_name}" |
| 178 | + ) |
| 179 | + |
| 180 | + self.selection_set: SelectionSetNode = SelectionSetNode( |
| 181 | + selections=FrozenList(DSLField.get_ast_fields(all_fields)) |
| 182 | + ) |
| 183 | + |
| 184 | + |
| 185 | +class DSLQuery(DSLOperation): |
| 186 | + operation_type = OperationType.QUERY |
| 187 | + |
| 188 | + |
| 189 | +class DSLMutation(DSLOperation): |
| 190 | + operation_type = OperationType.MUTATION |
| 191 | + |
| 192 | + |
| 193 | +class DSLSubscription(DSLOperation): |
| 194 | + operation_type = OperationType.SUBSCRIPTION |
| 195 | + |
| 196 | + |
136 | 197 | class DSLType:
|
137 | 198 | """The DSLType represents a GraphQL type for the DSL code.
|
138 | 199 |
|
@@ -270,6 +331,7 @@ def select(
|
270 | 331 | of the :class:`DSLField` class.
|
271 | 332 | """
|
272 | 333 |
|
| 334 | + # Concatenate fields without and with alias |
273 | 335 | added_fields: Tuple["DSLField", ...] = self.get_aliased_fields(
|
274 | 336 | fields, fields_with_alias
|
275 | 337 | )
|
|
0 commit comments