示例#1
0
def _get_type_mapping_from_actual_type(
        root) -> typing.Dict[typing.Any, typing.Type]:
    # we map ~T to the actual type of root
    type_var_to_actual_type = {}

    for field_name, annotation in root.__annotations__.items():
        # when we have a list we want to get the type of the elements contained in the
        # list, to do so we currently only get the first time (if the list is not empty)
        # this might break in more complex cases, but should suffice for now.

        if is_list(annotation):
            annotation = get_list_annotation(annotation)

            if is_type_var(annotation):
                values = getattr(root, field_name)

                if values:
                    type_var_to_actual_type[annotation] = type(values[0])

        elif is_type_var(annotation):
            type_var_to_actual_type[annotation] = type(
                getattr(root, field_name))

        elif is_generic(annotation):
            type_var_to_actual_type.update(
                _get_type_mapping_from_actual_type(getattr(root, field_name)))

    return type_var_to_actual_type
示例#2
0
    def type_params(self) -> Optional[List[Type]]:
        if self.is_list:
            assert self.child is not None
            return self.child.type_params

        if isinstance(self.type, StrawberryUnion):
            types = self.type.types
            type_vars = [t for t in types if is_type_var(t)]

            if type_vars:
                return type_vars

        if is_type_var(self.type):
            return [self.type]

        if has_type_var(self.type):
            return get_parameters(self.type)

        return None
示例#3
0
    def resolve(self) -> Union[StrawberryType, type]:
        annotation: object
        if isinstance(self.annotation, str):
            annotation = ForwardRef(self.annotation)
        else:
            annotation = self.annotation

        evaled_type = _eval_type(annotation, self.namespace, None)
        if evaled_type is None:
            raise ValueError("Annotation cannot be plain None type")
        if self._is_async_generator(evaled_type):
            evaled_type = self._strip_async_generator(evaled_type)
        if self._is_lazy_type(evaled_type):
            return evaled_type

        if self._is_generic(evaled_type):
            if any(is_type_var(type_) for type_ in evaled_type.__args__):
                return evaled_type
            return self.create_concrete_type(evaled_type)

        # Simply return objects that are already StrawberryTypes
        if self._is_strawberry_type(evaled_type):
            return evaled_type

        # Everything remaining should be a raw annotation that needs to be turned into
        # a StrawberryType
        if self._is_enum(evaled_type):
            return self.create_enum(evaled_type)
        if self._is_list(evaled_type):
            return self.create_list(evaled_type)
        elif self._is_optional(evaled_type):
            return self.create_optional(evaled_type)
        elif self._is_scalar(evaled_type):
            return evaled_type
        elif self._is_union(evaled_type):
            return self.create_union(evaled_type)
        elif is_type_var(evaled_type):
            return self.create_type_var(evaled_type)

        # TODO: Raise exception now, or later?
        # ... raise NotImplementedError(f"Unknown type {evaled_type}")
        return evaled_type
示例#4
0
def _get_type_params_for_field(
    field_definition: FieldDefinition, ) -> Optional[List[Type]]:
    if field_definition.is_list:
        child = cast(FieldDefinition, field_definition.child)

        return _get_type_params_for_field(child)

    type = cast(Type, field_definition.type)

    if isinstance(type, StrawberryUnion):
        types = type.types
        type_vars = [t for t in types if is_type_var(t)]

        if type_vars:
            return type_vars

    if is_type_var(type):
        return [type]

    if has_type_var(type):
        return get_parameters(type)

    return None
示例#5
0
def _resolve_generic_type(type: Type, field_name: str) -> Type:
    if hasattr(type, "_type_definition") and type._type_definition.is_generic:
        args = get_args(type)

        # raise an error when using generics without passing any type parameter, ie:
        # >>> class X(Generic[T]): ...
        # >>> a: X
        # instead of
        # >>> a: X[str]

        if len(args) == 0:
            raise MissingTypesForGenericError(field_name, type)

        # we only make a copy when all the arguments are not type vars
        if not all(is_type_var(a) for a in args):
            return copy_type_with(type, *args)

    return type
示例#6
0
def copy_type_with(
        base: Type,
        *types: Type,
        params_to_type: Dict[Type, Union[Type,
                                         StrawberryUnion]] = None) -> Type:
    if params_to_type is None:
        params_to_type = {}

    if isinstance(base, StrawberryUnion):
        return copy_union_with(base.types,
                               params_to_type=params_to_type,
                               description=base.description)

    if hasattr(base, "_type_definition"):
        definition = cast(TypeDefinition, base._type_definition)

        if definition.type_params:
            fields = []

            type_params = definition.type_params.values()

            for param, type_ in zip(type_params, types):
                if is_union(type_):
                    params_to_type[param] = copy_union_with(
                        type_.__args__, params_to_type=params_to_type)
                else:
                    params_to_type[param] = type_

            name = get_name_from_types(
                params_to_type.values()) + definition.name

            for field in definition.fields:
                kwargs = dataclasses.asdict(field)

                if field.is_list:
                    child = cast(FieldDefinition, field.child)
                    child_type = cast(Type, child.type)

                    # TODO: nested list

                    kwargs["child"] = FieldDefinition(
                        name=child.name,
                        origin=child.origin,
                        origin_name=child.origin_name,
                        is_optional=child.is_optional,
                        type=copy_type_with(child_type,
                                            params_to_type=params_to_type),
                    )

                else:
                    field_type = cast(Type, field.type)

                    kwargs["type"] = copy_type_with(
                        field_type, params_to_type=params_to_type)

                federation_args = kwargs.pop("federation")
                kwargs["federation"] = FederationFieldParams(**federation_args)

                fields.append(FieldDefinition(**kwargs))

            type_definition = TypeDefinition(
                name=name,
                is_input=definition.is_input,
                origin=definition.origin,
                is_interface=definition.is_interface,
                is_generic=False,
                federation=definition.federation,
                interfaces=definition.interfaces,
                description=definition.description,
                _fields=fields,
            )
            type_definition._type_params = {}

            copied_type = builtins.type(
                name,
                (),
                {"_type_definition": type_definition},
            )

            if not hasattr(base, "_copies"):
                base._copies = {}

            base._copies[types] = copied_type

            return copied_type

    if is_type_var(base):
        # TODO: we ignore the type issue here as we'll improve how types
        # are represented internally (using StrawberryTypes) so we can improve
        # typings later
        return params_to_type[base]  # type: ignore

    return base
示例#7
0
def resolve_type(
        field_definition: Union[FieldDefinition, ArgumentDefinition]) -> None:
    # convert a python type to include a strawberry definition, so for example
    # Union becomes a class with a UnionDefinition, Generics become an actual
    # type definition. This helps with making the code to convert the type definitions
    # to GraphQL types, as we only have to deal with Python's typings in one place.

    type = cast(Type, field_definition.type)
    origin_name = cast(str, field_definition.origin_name)

    if isinstance(type, LazyType):
        field_definition.type = type.resolve_type()

    if isinstance(type, str):
        module = sys.modules[field_definition.origin.__module__].__dict__

        type = eval(type, module)
        field_definition.type = type

    if is_forward_ref(type):
        # if the type is a forward reference we try to resolve the type by
        # finding it in the global namespace of the module where the field
        # was initially declared. This will break when the type is not declared
        # in the main scope, but we don't want to support that use case
        # see https://mail.python.org/archives/list/[email protected]/thread/SNKJB2U5S74TWGDWVD6FMXOP63WVIGDR/  # noqa: E501

        type_name = type.__forward_arg__

        module = sys.modules[field_definition.origin.__module__]

        # TODO: we should probably raise an error if we can't find the type
        type = module.__dict__[type_name]

        field_definition.type = type

        return

    if is_async_generator(type):
        # TODO: shall we raise a warning if field is not used in a subscription?

        # async generators are used in subscription, we only need the yield type
        # https://docs.python.org/3/library/typing.html#typing.AsyncGenerator
        field_definition.type = get_async_generator_annotation(type)

        return resolve_type(field_definition)

    # check for Optional[A] which is represented as Union[A, None], we
    # have an additional check for proper unions below
    if is_optional(type) and len(type.__args__) == 2:
        # this logics works around List of optionals and Optional lists of Optionals:
        # >>> Optional[List[Str]]
        # >>> Optional[List[Optional[Str]]]
        # the field is only optional if it is not a list or if it was already optional
        # since we mark the child as optional when the field is a list

        field_definition.is_optional = (True and not field_definition.is_list
                                        or field_definition.is_optional)
        field_definition.is_child_optional = field_definition.is_list
        field_definition.type = get_optional_annotation(type)

        return resolve_type(field_definition)

    elif is_list(type):
        # TODO: maybe this should be an argument definition when it is argument
        # but doesn't matter much
        child_definition = FieldDefinition(
            origin=field_definition.origin,  # type: ignore
            name=None,
            origin_name=None,
            type=get_list_annotation(type),
        )

        resolve_type(child_definition)

        field_definition.type = None
        field_definition.is_list = True
        field_definition.child = child_definition

        return

    # case for Union[A, B, C], it also handles Optional[Union[A, B, C]] as optionals
    # type hints are represented as Union[..., None].

    elif is_union(type):
        # Optional[Union[A, B]] is represented as Union[A, B, None] so we need
        # too check again if the field is optional as the check above only checks
        # for single Optionals
        field_definition.is_optional = is_optional(type)

        types = type.__args__

        # we use a simplified version of resolve_type since unions in GraphQL
        # are simpler and cannot contain lists or optionals

        types = tuple(
            _resolve_generic_type(t, origin_name) for t in types
            if t is not None.__class__)

        field_definition.is_union = True
        field_definition.type = union(get_name_from_types(types), types)

    # case for Type[A], we want to convert generics to have the concrete types
    # when we pass them, so that we don't have to deal with generics when
    # generating the GraphQL types later on.

    elif hasattr(type,
                 "_type_definition") and type._type_definition.is_generic:
        args = get_args(type)

        # raise an error when using generics without passing any type parameter, ie:
        # >>> class X(Generic[T]): ...
        # >>> a: X
        # instead of
        # >>> a: X[str]

        if len(args) == 0:
            name = cast(str, field_definition.origin_name)

            raise MissingTypesForGenericError(name, type)

        # we only make a copy when all the arguments are not type vars
        if not all(is_type_var(a) for a in args):
            field_definition.type = copy_type_with(type, *args)

    if isinstance(type, StrawberryUnion):
        field_definition.is_union = True
示例#8
0
def copy_type_with(
    base: Type, *types: Type, params_to_type: Dict[Type, Type] = None
) -> Type:
    if params_to_type is None:
        params_to_type = {}

    if hasattr(base, "_union_definition"):
        types = cast(
            Tuple[Type],
            tuple(
                copy_type_with(t, params_to_type=params_to_type)
                for t in base._union_definition.types
            ),
        )

        return union(
            name=get_name_from_types(types),
            types=types,
            description=base._union_definition.description,
        )

    if hasattr(base, "_type_definition"):
        definition = cast(TypeDefinition, base._type_definition)

        if definition.type_params:
            fields = []

            type_params = definition.type_params.values()
            params_to_type.update(dict(zip(type_params, types)))

            name = get_name_from_types(params_to_type.values()) + definition.name

            for field in definition.fields:
                kwargs = dataclasses.asdict(field)

                if field.is_list:
                    child = cast(FieldDefinition, field.child)
                    child_type = cast(Type, child.type)

                    # TODO: nested list

                    kwargs["child"] = FieldDefinition(
                        name=child.name,
                        origin=child.origin,
                        origin_name=child.origin_name,
                        is_optional=child.is_optional,
                        type=copy_type_with(child_type, params_to_type=params_to_type),
                    )

                else:
                    field_type = cast(Type, field.type)

                    kwargs["type"] = copy_type_with(
                        field_type, params_to_type=params_to_type
                    )

                federation_args = kwargs.pop("federation")
                kwargs["federation"] = FederationFieldParams(**federation_args)

                fields.append(FieldDefinition(**kwargs))

            type_definition = TypeDefinition(
                name=name,
                is_input=definition.is_input,
                origin=definition.origin,
                is_interface=definition.is_interface,
                is_generic=False,
                federation=definition.federation,
                interfaces=definition.interfaces,
                description=definition.description,
                _fields=fields,
            )
            type_definition._type_params = {}

            copied_type = builtins.type(
                name, (), {"_type_definition": type_definition},
            )

            if not hasattr(base, "_copies"):
                base._copies = {}

            base._copies[types] = copied_type

            return copied_type

    if is_type_var(base):
        return params_to_type[base]

    return base
示例#9
0
def copy_type_with(
        base: Type,
        *types: Type,
        params_to_type: Dict[Type, Union[Type,
                                         StrawberryUnion]] = None) -> Type:
    if params_to_type is None:
        params_to_type = {}

    if isinstance(base, StrawberryUnion):
        return copy_union_with(base.types,
                               params_to_type=params_to_type,
                               description=base.description)

    if hasattr(base, "_type_definition"):
        definition = cast(TypeDefinition, base._type_definition)

        if definition.type_params:
            fields = []

            type_params = definition.type_params.values()

            for param, type_ in zip(type_params, types):
                if is_union(type_):
                    params_to_type[param] = copy_union_with(
                        type_.__args__, params_to_type=params_to_type)
                else:
                    params_to_type[param] = type_

            name = get_name_from_types(
                params_to_type.values()) + definition.name

            for field in definition.fields:

                # Copy federation information
                federation = FederationFieldParams(**field.federation.__dict__)

                new_field = StrawberryField(
                    python_name=field.python_name,
                    graphql_name=field.graphql_name,
                    origin=field.origin,
                    type_=field.type,
                    default_value=field.default_value,
                    base_resolver=field.base_resolver,
                    child=field.child,
                    is_child_optional=field.is_child_optional,
                    is_list=field.is_list,
                    is_optional=field.is_optional,
                    is_subscription=field.is_subscription,
                    is_union=field.is_union,
                    federation=federation,
                    permission_classes=field.permission_classes,
                )

                if field.is_list:
                    assert field.child is not None

                    child_type = copy_type_with(field.child.type,
                                                params_to_type=params_to_type)

                    new_field.child = StrawberryField(
                        python_name=field.child.python_name,
                        origin=field.child.origin,
                        graphql_name=field.child.graphql_name,
                        is_optional=field.child.is_optional,
                        type_=child_type,
                    )

                else:
                    new_field.type = copy_type_with(
                        field.type, params_to_type=params_to_type)

                fields.append(new_field)

            type_definition = TypeDefinition(
                name=name,
                is_input=definition.is_input,
                origin=definition.origin,
                is_interface=definition.is_interface,
                is_generic=False,
                federation=definition.federation,
                interfaces=definition.interfaces,
                description=definition.description,
                _fields=fields,
            )
            type_definition._type_params = {}

            copied_type = builtins.type(
                name,
                (base.__origin__, ) if hasattr(base, "__origin__") else (),
                {"_type_definition": type_definition},
            )

            if not hasattr(base, "_copies"):
                base._copies = {}

            base._copies[types] = copied_type

            return copied_type

    if is_type_var(base):
        # TODO: we ignore the type issue here as we'll improve how types
        # are represented internally (using StrawberryTypes) so we can improve
        # typings later
        return params_to_type[base]  # type: ignore

    return base