def merge_types(name: str, types: Tuple[type, ...]) -> type: """Merge multiple Strawberry types into one For example, given two queries `A` and `B`, one can merge them into a super type as follows: merge_types("SuperQuery", (B, A)) This is essentially the same as: class SuperQuery(B, A): ... """ if not types: raise ValueError("Can't merge types if none are supplied") fields = chain(*(t._type_definition.fields for t in types) # type: ignore[attr-defined] ) counter = Counter(f.name for f in fields) dupes = [f for f, c in counter.most_common() if c > 1] if dupes: warnings.warn("{} has overridden fields: {}".format( name, ", ".join(dupes))) return strawberry.type(type(name, types, {}))
def create_type(name: str, fields: List[StrawberryField]) -> Type: """Create a Strawberry type from a list of StrawberryFields >>> @strawberry.field >>> def hello(info) -> str: >>> return "World" >>> >>> Query = create_type(name="Query", fields=[hello]) """ if not fields: raise ValueError(f'Can\'t create type "{name}" with no fields') namespace = {} annotations = {} for field in fields: if not isinstance(field, StrawberryField): raise TypeError("Field is not an instance of StrawberryField") if field.python_name is None: raise ValueError( ("Field doesn't have a name. Fields passed to " "`create_type` must define a name by passing the " "`name` argument to `strawberry.field`.")) namespace[field.python_name] = field annotations[field.python_name] = field.type namespace["__annotations__"] = annotations # type: ignore cls = types.new_class(name, (), {}, lambda ns: ns.update(namespace)) return strawberry.type(cls)
def create_type(name: str, fields: List[StrawberryField]) -> Type: """Create a Strawberry type from a list of StrawberryFields >>> @strawberry.field >>> def hello(info) -> str: >>> return "World" >>> >>> Query = create_type(name="Query", fields=[hello]) """ if len(fields) == 0: raise ValueError(f'Can\'t create type "{name}" with no fields') dataclass_fields = [] for field in fields: if not isinstance(field, StrawberryField): raise TypeError("Field is not an instance of StrawberryField") if field.graphql_name is None: raise ValueError( ("Field doesn't have a name. Fields passed to " "`create_type` must define a name by passing the " "`name` argument to `strawberry.field`.")) dataclass_fields.append(( field.graphql_name, field.type, dataclasses_field(default=field), )) cls = make_dataclass(name, fields=dataclass_fields) return strawberry.type(cls)
def mutation(cls): object_name = utils.camel_to_snake(cls.model._meta.object_name) class Mutation: pass setattr(Mutation, f'create_{object_name}', cls.create_mutation()) setattr(Mutation, f'create_{object_name}s', cls.batch_create_mutation()) setattr(Mutation, f'update_{object_name}s', cls.update_mutation()) setattr(Mutation, f'delete_{object_name}s', cls.delete_mutation()) return strawberry.type(Mutation)
def create_error_type(base_name, graphql_fields): fields = [(name, List[str], dataclasses.field(default_factory=list)) for name, _ in graphql_fields.items()] fields.append( ("nonFieldErrors", List[str], dataclasses.field(default_factory=list))) error_type = dataclasses.make_dataclass(f"{base_name}Errors", fields) return strawberry.type(error_type)
def generate_model_type(resolver_cls, is_input=False, is_update=False): model = resolver_cls.model annotations = {} attributes = {'__annotations__': annotations} # add fields for field in model._meta.get_fields(): if resolver_cls.fields and field.name not in resolver_cls.fields: continue # skip if is_in(field.name, resolver_cls.exclude): continue # skip if is_input and is_in(field.name, resolver_cls.readonly_fields): continue # skip if field.is_relation: if isinstance(field, fields.related.ForeignKey): field_params = get_relation_foreignkey_field( field, is_input, is_update) else: if is_input: continue field_params = get_relation_field(field) else: field_params = get_field(field, is_input, is_update) if not field_params: continue field_name, field_type, field_kwargs = field_params if is_input: attributes[field_name] = strawberry.arguments.UNSET else: if resolver_cls.field_permission_classes: field_kwargs[ 'permission_classes'] = resolver_cls.field_permission_classes attributes[field_name] = strawberry.field(**field_kwargs) if field_type: annotations[field_name] = field_type if not is_input: for field_name in dir(resolver_cls): field = getattr(resolver_cls, field_name) if hasattr(field, '_field_definition'): attributes[field_name] = field # generate type type_name = model._meta.object_name if is_update: type_name = f'Update{type_name}' elif is_input: type_name = f'Create{type_name}' model_type = type(type_name, (), attributes) model_type = strawberry.type(model_type, is_input=is_input) if not is_input: register_model_type(model, model_type) register_resolver_cls(model, resolver_cls) return model_type
def create_mutation_type(name: str, mutations: List[StrawberryField]): cls = make_dataclass( name, fields=[( mutation._field_definition.origin_name, mutation._field_definition.type, field(default=strawberry.mutation( mutation._field_definition.origin)), ) for mutation in mutations], ) return strawberry.type(cls, name=name)
def create_validation_error_type(prefix: str, type_: StrawberryField): @classmethod def from_validation_error(cls, validation_error: pydantic.ValidationError): errors = validation_error.errors() payload = {} for error in errors: field = error["loc"][0] message = error["msg"] error_type = error["type"] errors = payload.setdefault(field, []) errors.append(FieldError(message=message, type=error_type)) return cls(errors=type_(**payload)) cls = make_dataclass(f"{prefix}ValidationError", [("errors", type_)]) cls.from_validation_error = from_validation_error return strawberry.type(cls)
def test_create_type_field_invalid(): with pytest.raises(TypeError): create_type("MyType", [strawberry.type()])
def query(cls): object_name = utils.camel_to_snake(cls.model._meta.object_name) class Query: pass setattr(Query, f'{object_name}', cls.get_field()) setattr(Query, f'{object_name}s', cls.list_field()) return strawberry.type(Query)
def obj(cls): return strawberry.type(cls)
def graphql(cls): if Enum in cls.__mro__: return strawberry.enum(cls) return strawberry.type(cls)