def contain_sensitive_field(self, node: GraphQLNode, type_def) -> bool: if isinstance(node, FragmentSpread) or not node.selection_set: return False fields: Dict[str, GraphQLField] = {} if isinstance(type_def, (GraphQLObjectType, GraphQLInterfaceType)): fields = type_def.fields for child_node in node.selection_set.selections: if isinstance(child_node, Field): field = fields.get(child_node.name.value) if not field: continue field_type = get_named_type(field.type) if type_def and type_def.name: self.is_sensitive_field(child_node, type_def.name) self.contain_sensitive_field(child_node, field_type) if isinstance(child_node, FragmentSpread): fragment = self.context.get_fragment(child_node.name.value) if fragment: fragment_type = self.context.get_schema().get_type( fragment.type_condition.name.value) self.contain_sensitive_field(fragment, fragment_type) if isinstance(child_node, InlineFragment): inline_fragment_type = type_def if child_node.type_condition and child_node.type_condition.name: inline_fragment_type = self.context.get_schema().get_type( child_node.type_condition.name.value) self.contain_sensitive_field(child_node, inline_fragment_type) return False
def get_type_literal(type_: GraphQLType, optional: bool = False) -> str: """ String! => str String => Optional[str] [Character!]! => ['Character'] [Character!] => Optional['Character'] [Character] => Optional[List[Optional['Character']]] """ is_null = False if is_non_null_type(type_): type_ = cast(GraphQLWrappingType, type_).of_type else: is_null = True if is_wrapping_type(type_): type_ = cast(GraphQLWrappingType, type_) value = get_type_literal(type_.of_type) if is_list_type(type_): value = f'List[{value}]' else: type_ = get_named_type(type_) value = SCALAR_MAP.get(type_.name) or type_.name value = value if is_leaf_type(type_) else f"'{value}'" # value = value if is_leaf_type(type_) or is_interface_type(type_) else f"'{value}'" if optional or is_null: value = f'Optional[{value}]' return value
def _get_args(fields, field): args = {} if field in fields and fields[field].args: args = ({ arg_name: get_named_type(arg.type).name for arg_name, arg in fields[field].args.items() }) return args
def connection_definitions( node_type: Union[GraphQLNamedOutputType, GraphQLNonNull[GraphQLNamedOutputType]], name: Optional[str] = None, resolve_node: Optional[GraphQLFieldResolver] = None, resolve_cursor: Optional[GraphQLFieldResolver] = None, edge_fields: Optional[Thunk[GraphQLFieldMap]] = None, connection_fields: Optional[Thunk[GraphQLFieldMap]] = None, ) -> GraphQLConnectionDefinitions: """Return GraphQLObjectTypes for a connection with the given name. The nodes of the returned object types will be of the specified type. """ name = name or get_named_type(node_type).name edge_type = GraphQLObjectType( name + "Edge", description="An edge in a connection.", fields=lambda: { "node": GraphQLField( node_type, resolve=resolve_node, description="The item at the end of the edge", ), "cursor": GraphQLField( GraphQLNonNull(GraphQLString), resolve=resolve_cursor, description="A cursor for use in pagination", ), **resolve_maybe_thunk(edge_fields or {}), }, ) connection_type = GraphQLObjectType( name + "Connection", description="A connection to a list of items.", fields=lambda: { "pageInfo": GraphQLField( GraphQLNonNull(page_info_type), description="Information to aid in pagination.", ), "edges": GraphQLField(GraphQLList(edge_type), description="A list of edges."), **resolve_maybe_thunk(connection_fields or {}), }, ) return GraphQLConnectionDefinitions(edge_type, connection_type)
async def resolve(self, _next, _resource, _info, **kwargs): mock_header = self.get_mocks_from_headers(_info.context) mock_objects = self.get_mocks(extra=mock_header) if not mock_objects: return await self.run_next(_next, _resource, _info, **kwargs) field_name = _info.field_name return_type = _info.return_type named_type = get_named_type(return_type) parent_type_name = _info.parent_type.name resolver_is_mocked = ( self.mock_all or mock_objects.has_named_type(named_type.name) or mock_objects.has_parent_field(parent_type_name, field_name) ) if not resolver_is_mocked: return await self.run_next(_next, _resource, _info, **kwargs) if mock_objects.has_parent_field(parent_type_name, field_name): return mock_objects.get_parent_field(parent_type_name, field_name) # Check if we previously resolved a mock object. field_value = ( _resource.get(field_name) if isinstance(_resource, dict) else getattr(_resource, field_name, None) ) if field_value: return field_value # Finally check the return_type for mocks def resolve_return_type(schema_type: GraphQLType): # Special case for list types return a random length list of type. if isinstance(schema_type, GraphQLList): list_length = mock_objects.get(LIST_LENGTH_KEY, 3) return [resolve_return_type(schema_type.of_type) for x in range(list_length)] named_type_name = get_named_type(schema_type).name if named_type_name in mock_objects: return mock_objects.get(named_type_name) # If we reached this point this is a custom type that has not # explicitly overridden in the mock_objects. Just return a dict # with a `__typename` set to the type name to assist in resolving # Unions and Interfaces. return SuperDict({'__typename': named_type_name}) return resolve_return_type(return_type)
def is_there_field_annotations(_type): """ Check if a given type contains fields that does not start with '_' and are of enum or scalar type These fields should be annotations :param type: :return boolean: """ for field_name, field_type in _type.fields.items(): if field_name.startswith('_') or field_name == 'id': continue if is_enum_or_scalar(get_named_type(field_type.type)): return True return False
def generate(input_file, output_file): # load schema with open(input_file, 'r') as f: schema_string = f.read() schema = build_schema(schema_string) data = {'types': [], 'interfaces': [], 'unions': []} # get list of types for type_name, _type in schema.type_map.items(): if is_interface_type(_type): t = {'name': type_name, 'possible_types': []} for possible_type in schema.get_possible_types(_type): t['possible_types'].append(possible_type.name) data['interfaces'].append(t) if is_union_type(_type): t = {'name': type_name, 'possible_types': []} for possible_type in schema.get_possible_types(_type): t['possible_types'].append(possible_type.name) data['unions'].append(t) if is_schema_defined_object_type(_type): t = {'name': type_name, 'fields': []} # add object fields for field_name, field_type in _type.fields.items(): inner_field_type = get_named_type(field_type.type) if is_schema_defined_object_type(inner_field_type) or \ is_interface_type(inner_field_type) or \ is_union_type(inner_field_type): t['fields'].append(field_name) sort_before_rendering(t) data['types'].append(t) # sort data['types'].sort(key=lambda x: x['name']) data['interfaces'].sort() data['unions'].sort() # apply template template = Template(filename=f'resources/resolver.template') if output_file is None: print(template.render(data=data)) else: with open(output_file, 'w') as f: updated_schema_string = template.render(data=data) api_schema = build_schema(schema_string) assert_valid_schema(api_schema) f.write(updated_schema_string)
def resolve_return_type(schema_type: GraphQLType): # Special case for list types return a random length list of type. if isinstance(schema_type, GraphQLList): list_length = mock_objects.get(LIST_LENGTH_KEY, 3) return [resolve_return_type(schema_type.of_type) for x in range(list_length)] named_type_name = get_named_type(schema_type).name if named_type_name in mock_objects: return mock_objects.get(named_type_name) # If we reached this point this is a custom type that has not # explicitly overridden in the mock_objects. Just return a dict # with a `__typename` set to the type name to assist in resolving # Unions and Interfaces. return SuperDict({'__typename': named_type_name})
def map_schema_types(schema, ignore=['Query', 'Mutation']): """Iterate over a graphQl schema to get the schema types and its relations The process ignores the root types like Query and Mutation and the Scalar types""" objects = dict() enums = dict() for type_name, type_obj in schema.type_map.items(): # Iterating over all the items of the schema # Ignore internal types and root types if (type_name[:2] != '__') and (type_name not in ignore): # ENUM types if is_enum_type(type_obj): enums[type_name] = [] # values # Graphql Types elif is_object_type(type_obj): #Create the type obj obj = _get_object(objects, type_name) # Iterate over all fields of the graphql type for field_name, field_obj in type_obj.fields.items(): field = get_named_type( field_obj.type) # Returns a scalar or an object # setting non null nullable = True if contains_non_null_type(field_obj.type): nullable = False if is_scalar_type(field): obj['field'][field_name] = { 'type': field.name, 'nullable': nullable } elif is_object_type(field): # if it's a list of types, that means that I'm the parent in a one to many relationship list_ = False if contains_list_type(field_obj.type): list_ = True obj['relationship'][field_name] = { 'type': field.name, 'nullable': nullable, 'list': list_ } return {'objects': objects, 'enums': enums}
def compute_node_cost(self, node, type_definition, parent_multiplier=None, parent_complexity=None): if not parent_multiplier: parent_multiplier = [] if not node.selection_set: return 0 fields = {} if isinstance(type_definition, GraphQLObjectType) or isinstance( type_definition, GraphQLInterfaceType): fields = type_definition.fields total_cost = 0 fragment_costs = [] variables = {} # TODO get variables from operation selections = node.selection_set.selections for selection in selections: self.operation_multipliers = [*parent_multiplier] node_cost = 0 if selection.kind == 'field': # Calculate cost for FieldNode field: GraphQLField = fields.get(selection.name.value) if not field: break field_type = get_named_type(field.type) field_args = get_argument_values(field, selection, variables) use_field_type_complexity = False cost_is_computed = False if field.ast_node and field.ast_node.directives: directive_args: Union[ Tuple[int, List, bool], None] = self.get_args_from_directives( directives=field.ast_node.directives, field_args=field_args) override_complexity = directive_args[-1] if not override_complexity and isinstance( field_type, GraphQLObjectType): use_field_type_complexity = True parent_complexity, _, _ = self.get_args_from_directives( directives=field_type.ast_node.directives, field_args=field_args) node_cost = self.compute_cost(directive_args) if directive_args: cost_is_computed = True if field_type and field_type.ast_node and \ field_type.ast_node.directives and \ isinstance(field_type, GraphQLObjectType) and \ (not cost_is_computed or use_field_type_complexity): directive_args = self.get_args_from_directives( directives=field_type.ast_node.directives, field_args=field_args) node_cost = self.compute_cost(directive_args) child_cost = self.compute_node_cost( node=selection, type_definition=field_type, parent_multiplier=self.operation_multipliers, parent_complexity=parent_complexity) or 0 node_cost += child_cost elif selection.kind == 'fragment_spread': fragment = self.context.get_fragment(selection.name.value) fragment_type = fragment and self.context.schema.get_type( fragment.type_condition.name.value) fragment_node_cost = self.compute_node_cost(fragment, fragment_type, self.operation_multipliers) \ if fragment \ else 0 fragment_costs.append(fragment_node_cost) node_cost = 0 elif selection.kind == 'inline_fragment': inline_fragment_type = self.context.schema.get_type(selection.type_condition.name.value) \ if selection.type_condition and selection.type_condition.name \ else type_definition fragment_node_cost = self.compute_node_cost(selection, inline_fragment_type, self.operation_multipliers) \ if selection \ else 0 fragment_costs.append(fragment_node_cost) node_cost = 0 else: node_cost = self.compute_node_cost( node=selection, type_definition=type_definition) total_cost += max(node_cost, 0) if fragment_costs: return total_cost + max(fragment_costs) return total_cost
def compute_node_cost(self, node: CostAwareNode, type_def, parent_multipliers=None): if parent_multipliers is None: parent_multipliers = [] if isinstance(node, FragmentSpread) or not node.selection_set: return 0 fields: GraphQLFieldMap = {} if isinstance(type_def, (GraphQLObjectType, GraphQLInterfaceType)): fields = type_def.fields total = 0 for child_node in node.selection_set.selections: self.operation_multipliers = parent_multipliers[:] node_cost = self.default_cost if isinstance(child_node, Field): field = fields.get(child_node.name.value) if not field: continue field_type = get_named_type(field.type) try: field_args: Dict[str, Any] = get_argument_values( field.args, child_node.arguments, self.variables, ) except Exception as e: report_error(self.context, e) field_args = {} if not self.cost_map: return 0 cost_map_args = ( self.get_args_from_cost_map(child_node, type_def.name, field_args) if type_def and type_def.name else None ) if cost_map_args is not None: try: node_cost = self.compute_cost(**cost_map_args) except (TypeError, ValueError) as e: report_error(self.context, e) child_cost = self.compute_node_cost( child_node, field_type, self.operation_multipliers ) node_cost += child_cost if isinstance(child_node, FragmentSpread): fragment = self.context.get_fragment(child_node.name.value) if fragment: fragment_type = self.context.get_schema().get_type( fragment.type_condition.name.value ) node_cost = self.compute_node_cost(fragment, fragment_type) if isinstance(child_node, InlineFragment): inline_fragment_type = type_def if child_node.type_condition and child_node.type_condition.name: inline_fragment_type = self.context.get_schema().get_type( child_node.type_condition.name.value ) node_cost = self.compute_node_cost(child_node, inline_fragment_type) total += node_cost return total
def generate(input_file, output_dir, config: dict): # load schema with open(input_file, 'r') as f: schema_string = f.read() schema = build_schema(schema_string) data = { 'types': [], 'types_by_key': [], 'interfaces': [], 'unions': [], 'typeDelete': [], 'edge_types_to_delete': [], 'edge_types_to_update': [], 'edge_objects': [] } # get list of types for type_name, _type in schema.type_map.items(): if is_union_type(_type): data['unions'].append(type_name) continue if is_interface_type(_type): data['interfaces'].append(type_name) if is_union_type(_type): data['unions'].append(type_name) if is_edge_type(_type): if config.get('generation').get('query_edge_by_id'): data['edge_objects'].append(type_name) if is_schema_defined_object_type(_type): t = { 'Name': type_name, 'name': camelCase(type_name), 'fields': [], 'edgeFields': [], 'edgeFieldEndpoints': [], 'DateTime': [], 'hasKeyDirective': f'_KeyFor{type_name}' in schema.type_map.keys() } # add object fields for field_name, field_type in _type.fields.items(): inner_field_type = get_named_type(field_type.type) if field_name.startswith('_incoming') or field_name.startswith( '_outgoing'): t['edgeFields'].append(field_name) elif is_schema_defined_object_type( inner_field_type) or is_interface_type( inner_field_type) or is_union_type( inner_field_type): t['fields'].append(field_name) if inner_field_type.name == 'DateTime': t['DateTime'].append(field_name) if field_name[0] == '_': continue if is_schema_defined_object_type( inner_field_type) or is_interface_type( inner_field_type) or is_union_type( inner_field_type): t['edgeFieldEndpoints'].append( (pascalCase(field_name), inner_field_type)) if config.get('generation').get('delete_edge_objects'): data['edge_types_to_delete'].append( (f'{pascalCase(field_name)}EdgeFrom{type_name}', type_name)) if config.get('generation').get('update_edge_objects'): if is_there_field_annotations(schema.type_map[ f'_{pascalCase(field_name)}EdgeFrom{type_name}'] ): data['edge_types_to_update'].append(( f'{pascalCase(field_name)}EdgeFrom{type_name}', type_name)) sort_before_rendering(t) data['types'].append(t) if config.get('generation').get('delete_objects'): data['typeDelete'].append(type_name) # sort data['types'].sort(key=lambda x: x['name']) data['interfaces'].sort() data['unions'].sort() data['typeDelete'].sort() data['edge_types_to_delete'].sort() data['edge_types_to_update'].sort() data['edge_objects'].sort() # apply template template = Template(filename=f'resources/resolver.template') if output_dir is None: print(template.render(data=data)) else: with open(f'{output_dir}/resolvers.js', 'w') as f: updated_schema_string = template.render(data=data) api_schema = build_schema(schema_string) assert_valid_schema(api_schema) f.write(updated_schema_string)
def complete_field( context: 'QueryPlanningContext', scope: Scope[GraphQLCompositeType], parent_group: 'FetchGroup', path: ResponsePath, fields: FieldSet, ) -> Field: field_node = fields[0].field_node field_def = fields[0].field_def return_type = get_named_type(field_def.type) if not is_composite_type(return_type): # FIXME: We should look at all field nodes to make sure we take directives # into account (or remove directives for the time being). return Field(scope=scope, field_node=field_node, field_def=field_def) else: # For composite types, we need to recurse. field_path = add_path(path, get_response_name(field_node), field_def.type) sub_group = FetchGroup(parent_group.service_name) sub_group.merge_at = field_path sub_group.provided_fields = context.get_provided_fields( field_def, parent_group.service_name ) # For abstract types, we always need to request `__typename` if is_abstract_type(return_type): sub_group.fields.append( Field( scope=context.new_scope(cast(GraphQLCompositeType, return_type), scope), field_node=typename_field, field_def=GraphQLField(TypeNameMetaFieldDef, name='__typename'), ) ) sub_fields = collect_subfields(context, cast(GraphQLCompositeType, return_type), fields) # debug.group(() => `Splitting collected sub-fields (${debugPrintFields(subfields)})`); split_subfields(context, field_path, sub_fields, sub_group) # debug.groupEnd(); parent_group.other_dependent_groups.extend(sub_group.dependent_groups) selection_set = selection_set_from_field_set( sub_group.fields, cast(GraphQLCompositeType, return_type) ) if context.auto_fragmentization and len(sub_group.fields) > 2: internal_fragment = get_internal_fragment( selection_set, cast(GraphQLCompositeType, return_type), context ) definition = internal_fragment.definition selection_set = internal_fragment.selection_set parent_group.internal_fragments.add(definition) # "Hoist" internalFragments of the subGroup into the parentGroup so all # fragments can be included in the final request for the root FetchGroup parent_group.internal_fragments.update(sub_group.internal_fragments) new_field_node = copy(field_node) new_field_node.selection_set = selection_set return Field(scope=scope, field_node=new_field_node, field_def=field_def)