示例#1
0
def test_error_with_non_strawberry_type():
    @dataclass
    class A:
        a: int

    with pytest.raises(InvalidUnionType,
                       match="Type `A` cannot be used in a GraphQL Union"):
        strawberry.union("Result", (A, ))
示例#2
0
def test_error_with_non_strawberry_type():
    @dataclass
    class A:
        a: int

    with pytest.raises(InvalidUnionType,
                       match="Union type `A` is not a Strawberry type"):
        strawberry.union("Result", (A, ))
示例#3
0
def test_union_optional_with_or_operator():
    """
    Verify that the `|` operator is supported when annotating unions as
    optional in schemas.
    """

    @strawberry.type
    class Cat:
        name: str

    @strawberry.type
    class Dog:
        name: str

    animal_union = strawberry.union("Animal", (Cat, Dog))

    @strawberry.type
    class Query:
        @strawberry.field
        def animal(self) -> animal_union | None:
            return None

    schema = strawberry.Schema(query=Query)
    query = """{
        animal {
            __typename
        }
    }"""

    result = schema.execute_sync(query, root_value=Query())

    assert not result.errors
    assert result.data["animal"] is None
示例#4
0
def test_union_explicit_type_resolution():
    @dataclass
    class ADataclass:
        a: int

    @strawberry.type
    class A:
        a: int

        @classmethod
        def is_type_of(cls, obj, _info) -> bool:
            return isinstance(obj, ADataclass)

    @strawberry.type
    class B:
        b: int

    MyUnion = strawberry.union("MyUnion", types=(A, B))

    @strawberry.type
    class Query:
        @strawberry.field
        def my_field(self) -> MyUnion:
            return ADataclass(a=1)  # type: ignore

    schema = strawberry.Schema(query=Query)

    query = "{ myField { __typename, ... on A { a }, ... on B { b } } }"
    result = schema.execute_sync(query)

    assert not result.errors
    assert result.data == {"myField": {"__typename": "A", "a": 1}}
示例#5
0
def test_union_used_multiple_times():
    @strawberry.type
    class A:
        a: int

    @strawberry.type
    class B:
        b: int

    MyUnion = strawberry.union("MyUnion", types=(A, B))

    @strawberry.type
    class Query:
        field1: MyUnion
        field2: MyUnion

    schema = strawberry.Schema(query=Query)

    assert schema.as_str() == dedent(
        """\
        type A {
          a: Int!
        }

        type B {
          b: Int!
        }

        union MyUnion = A | B

        type Query {
          field1: MyUnion!
          field2: MyUnion!
        }"""
    )
示例#6
0
def test_union_inside_generics():
    @strawberry.type
    class Dog:
        name: str

    @strawberry.type
    class Cat:
        name: str

    @strawberry.type
    class Connection(Generic[T]):
        nodes: List[T]

    DogCat = strawberry.union("DogCat", (Dog, Cat))

    @strawberry.type
    class Query:
        connection: Connection[DogCat]

    query_definition = Query._type_definition
    assert query_definition.type_params == []

    [connection_field] = query_definition.fields
    assert connection_field.python_name == "connection"
    assert not isinstance(connection_field, StrawberryOptional)

    dog_cat_connection_definition = connection_field.type._type_definition

    [node_field] = dog_cat_connection_definition.fields
    assert isinstance(node_field.type, StrawberryList)

    union = dog_cat_connection_definition.fields[0].type.of_type
    assert isinstance(union, StrawberryUnion)
示例#7
0
def test_cannot_use_union_directly():
    @strawberry.type
    class A:
        a: int

    @strawberry.type
    class B:
        b: int

    Result = strawberry.union("Result", (A, B))

    with pytest.raises(ValueError, match=r"Cannot use union type directly"):
        Result()
示例#8
0
def test_named_union():
    @strawberry.type
    class A:
        a: int

    @strawberry.type
    class B:
        b: int

    Result = strawberry.union("Result", (A, B))

    union_type_definition = Result._union_definition

    assert union_type_definition.name == "Result"
    assert union_type_definition.types == (A, B)
示例#9
0
def test_named_union():
    @strawberry.type
    class A:
        a: int

    @strawberry.type
    class B:
        b: int

    Result = strawberry.union("Result", (A, B))

    union_type_definition = Result
    assert isinstance(union_type_definition, StrawberryUnion)
    assert union_type_definition.name == "Result"
    assert union_type_definition.types == (A, B)
示例#10
0
def test_union_as_an_argument_type():
    with pytest.raises(InvalidFieldArgument):

        @strawberry.type
        class Noun:
            text: str

        @strawberry.type
        class Verb:
            text: str

        Word = strawberry.union("Word", types=(Noun, Verb))

        @strawberry.field
        def add_word(word: Word) -> bool:
            return True
示例#11
0
def test_union_as_an_argument_type():
    error_message = 'Argument "word" on field "add_word" cannot be of type "Union"'
    with pytest.raises(InvalidFieldArgument, match=error_message):

        @strawberry.type
        class Noun:
            text: str

        @strawberry.type
        class Verb:
            text: str

        Word = strawberry.union("Word", types=(Noun, Verb))

        @strawberry.field
        def add_word(word: Word) -> bool:
            return True
示例#12
0
def test_named_union_description():
    global Result

    @strawberry.type
    class A:
        a: int

    @strawberry.type
    class B:
        b: int

    Result = strawberry.union("Result", (A, B), description="Example Result")

    @strawberry.type
    class Query:
        ab: Result = A(a=5)

    schema = strawberry.Schema(query=Query)

    query = """{
        __type(name: "Result") {
            kind
            description
        }

        ab {
            __typename,

            ... on A {
                a
            }
        }
    }"""

    result = schema.execute_sync(query, root_value=Query())

    assert not result.errors
    assert result.data["ab"] == {"__typename": "A", "a": 5}
    assert result.data["__type"] == {
        "kind": "UNION",
        "description": "Example Result"
    }

    del Result
示例#13
0
def test_union_inside_generics():
    global Connection, DogCat

    @strawberry.type
    class Dog:
        name: str

    @strawberry.type
    class Cat:
        name: str

    @strawberry.type
    class Connection(Generic[T]):
        nodes: List[T]

    DogCat = strawberry.union("DogCat", (Dog, Cat))

    @strawberry.type
    class Query:
        connection: Connection[DogCat]

    definition = Query._type_definition

    assert definition.name == "Query"
    assert len(definition.fields) == 1

    assert definition.type_params == {}
    assert definition.fields[0].name == "connection"
    assert definition.fields[0].is_optional is False

    type_definition = definition.fields[0].type._type_definition

    assert type_definition.name == "DogCatConnection"
    assert len(type_definition.fields) == 1
    assert type_definition.fields[0].is_list is True

    union_definition = type_definition.fields[0].child.type

    assert isinstance(union_definition, StrawberryUnion)
    assert union_definition.types[0]._type_definition.name == "Dog"
    assert union_definition.types[1]._type_definition.name == "Cat"

    del Connection, DogCat
示例#14
0
def test_union():
    @strawberry.type
    class Europe:
        name: str

    @strawberry.type
    class UK:
        name: str

    EU = strawberry.union("EU", types=(Europe, UK))

    @strawberry.type
    class WishfulThinking:
        desire: EU

    # TODO: Remove reference to ._type_definition with StrawberryObject
    field: StrawberryField = WishfulThinking._type_definition.fields[0]

    assert field.type is EU
示例#15
0
def test_union_with_generic():
    T = TypeVar("T")

    @strawberry.type
    class Error:
        message: str

    @strawberry.type
    class Edge(Generic[T]):
        node: T

    Result = strawberry.union("Result", (Error, Edge[str]))

    union_type_definition = Result
    assert isinstance(union_type_definition, StrawberryUnion)
    assert union_type_definition.name == "Result"
    assert union_type_definition.types[0] == Error

    assert union_type_definition.types[1]._type_definition.is_generic is False
    assert union_type_definition.types[1]._type_definition.name == "StrEdge"
示例#16
0
def test_can_use_union_in_generics():
    global Result

    @strawberry.type
    class A:
        a: int

    @strawberry.type
    class B:
        b: int

    Result = strawberry.union("Result", (A, B))

    @strawberry.type
    class Query:
        ab: Optional[Result] = None

    schema = strawberry.Schema(query=Query)

    query = """{
        __type(name: "Result") {
            kind
            description
        }

        ab {
            __typename,

            ... on A {
                a
            }
        }
    }"""

    result = schema.execute_sync(query, root_value=Query())

    assert not result.errors
    assert result.data["ab"] is None

    del Result
示例#17
0
def test_named_union():
    @strawberry.type
    class A:
        a: int

    @strawberry.type
    class B:
        b: int

    Result = strawberry.union("Result", (A, B))

    @strawberry.type
    class Query:
        ab: Result = A(a=5)

    schema = strawberry.Schema(query=Query)

    query = """{
        __type(name: "Result") {
            kind
            description
        }

        ab {
            __typename,

            ... on A {
                a
            }
        }
    }"""

    result = graphql_sync(schema, query, root_value=Query())

    assert not result.errors
    assert result.data["ab"] == {"__typename": "A", "a": 5}
    assert result.data["__type"] == {"kind": "UNION", "description": None}
示例#18
0
@strawberry.type
class UpdateProfileErrors:
    name: PydanticError = None
    full_name: PydanticError = None
    gender: PydanticError = None
    open_to_recruiting: PydanticError = None
    open_to_newsletter: PydanticError = None
    date_birth: PydanticError = None
    country: PydanticError = None


UpdateProfileValidationError = create_validation_error_type(
    "UpdateProfile", UpdateProfileErrors)

UpdateProfileResult = strawberry.union("UpdateProfileResult",
                                       (User, UpdateProfileValidationError))


@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_profile(input: UpdateProfileInput,
                         info: Info) -> UpdateProfileResult:
    try:
        input_model = input.to_pydantic()
    except pydantic.ValidationError as exc:
        return UpdateProfileValidationError.from_validation_error(exc)

    user = await service_update_profile(
        int(info.context.request.user.id),
        input_model,
        users_repository=info.context.users_repository,
    )
示例#19
0
@strawberry.type
class RegisterSuccess:
    user: User

    @classmethod
    def from_domain(cls, user: entities.User) -> RegisterSuccess:
        return cls(user=User.from_domain(user))


@strawberry.type
class EmailAlreadyUsed:
    message: str = "Email specified is already used"


RegisterResult = strawberry.union(
    "RegisterResult", (RegisterSuccess, EmailAlreadyUsed, RegisterValidationError)
)


@strawberry.mutation
async def register(info: Info, input: RegisterInput) -> RegisterResult:
    try:
        input_model = input.to_pydantic()
    except pydantic.ValidationError as exc:
        return RegisterValidationError.from_validation_error(exc)

    try:
        user = await services.register(
            input_model, users_repository=info.context.users_repository
        )
    except EmailAlreadyUsedError:
示例#20
0

@strawberry.type
class WrongEmailOrPassword:
    message: str = "Invalid username/password combination"


@strawberry.type
class LoginErrors:
    email: PydanticError = None
    password: PydanticError = None


LoginValidationError = create_validation_error_type("Login", LoginErrors)

LoginResult = strawberry.union(
    "LoginResult", (LoginSuccess, WrongEmailOrPassword, LoginValidationError))


@strawberry.mutation
async def login(info: Info, input: LoginInput) -> LoginResult:
    try:
        input_model = input.to_pydantic()
    except pydantic.ValidationError as exc:
        return LoginValidationError.from_validation_error(exc)

    try:
        user = await services.login(
            input_model, users_repository=info.context.users_repository)
    except (WrongEmailOrPasswordError, UserIsNotActiveError):
        return WrongEmailOrPassword()
示例#21
0
class NoSubscription:
    message: str = "No subscription to manage"


@strawberry.type
class NotSubscribedViaStripe:
    message: str = "Not subscribed via Stripe"


@strawberry.type
class CustomerPortalResponse:
    billing_portal_url: str


CustomerPortalResult = strawberry.union(
    "CustomerPortalResult",
    (CustomerPortalResponse, NoSubscription, NotSubscribedViaStripe),
)


@strawberry.mutation(permission_classes=[IsAuthenticated])
async def manage_user_subscription(
        info: Info[Context, Any]) -> CustomerPortalResult:
    try:
        billing_portal_url = await service_manage_user_association_subscription(
            info.context.request.user,
            association_repository=info.context.association_repository,
        )
        return CustomerPortalResponse(billing_portal_url=billing_portal_url)
    except (exceptions.CustomerNotAvailable,
            exceptions.NoSubscriptionAvailable):
        return NoSubscription()
示例#22
0
    def __init_subclass__(cls):
        name = cls.__name__

        form_class = cls.Meta.form_class
        form = form_class()

        form_fields = form.fields
        graphql_fields = convert_form_fields_to_fields(form_fields)

        input_type = None

        if len(graphql_fields) > 0:
            input_type = create_input_type(name, graphql_fields)

        error_type = create_error_type(name, graphql_fields)

        output_types = (
            cls.Meta.output_types if hasattr(cls.Meta, "output_types") else ()
        )

        output = strawberry.union(
            f"{name}Output", (error_type, *output_types), description="Output"
        )

        def _mutate(root, info, input: input_type) -> output:
            # Add the mutation input in the context so we can access it inside the permissions
            if info:
                setattr(info.context, "input", input)

            # Once we implement the permission in strawberry we can remove this :)
            if hasattr(cls.Meta, "permission_classes") and cls.Meta.permission_classes:
                for permission in cls.Meta.permission_classes:
                    if not permission().has_permission(root, info):
                        raise GraphQLError(permission.message)

            input_as_dict = input

            if dataclasses.is_dataclass(input):
                input_as_dict = dataclasses.asdict(input)

            form_kwargs = cls.get_form_kwargs(root, info, input_as_dict)
            form_kwargs["data"] = convert_enums_to_values(form_kwargs["data"])

            if "instance" in form_kwargs["data"] and issubclass(form_class, ModelForm):
                instance = form_kwargs["data"].pop("instance")
                instance = (
                    form_class.get_instance(instance)
                    if hasattr(form_class, "get_instance")
                    else form_class.Meta.model.objects.get(id=instance)
                )

                form = form_class(instance=instance, **form_kwargs)
                form.fields.pop("instance", None)
            else:
                form = form_class(**form_kwargs)

            error = cls.validate_form(form)

            if error:
                return error

            result = form.save()

            if hasattr(cls, "transform"):
                result = cls.transform(result)

            return result

        if input_type:

            def mutate(root, info, input: input_type) -> output:
                return _mutate(root, info, input)

        else:

            def mutate(root, info) -> output:
                return _mutate(root, info, {})

        # Hack because something changed and now the name contains `Mutation`
        # as the classname, which makes sense but why it didn't happen before? lol
        name = name.replace("Mutation", "")
        mutate = strawberry.mutation(mutate, name=f"{name[0].lower()}{name[1:]}")

        cls.Mutation = mutate
        cls.Meta.error_type = error_type
示例#23
0
def test_error_with_scalar_types():
    with pytest.raises(
            InvalidUnionType,
            match="Scalar type `int` cannot be used in a GraphQL Union"):
        strawberry.union("Result", (int, ))
示例#24
0
def test_error_with_empty_type_list():
    with pytest.raises(TypeError, match="No types passed to `union`"):
        strawberry.union("Result", [])
示例#25
0
    message: str = "Reset password token expired"


@strawberry.type
class ResetPasswordTokenInvalid:
    message: str = "Reset password token invalid"


ResetPasswordValidationError = create_validation_error_type(
    "ResetPassword", ResetPasswordErrors)

ResetPasswordResult = strawberry.union(
    "ResetPasswordResult",
    (
        ResetPasswordValidationError,
        OperationSuccess,
        ResetPasswordTokenExpired,
        ResetPasswordTokenInvalid,
    ),
)


@strawberry.mutation
async def reset_password(info: Info,
                         input: ResetPasswordInput) -> ResetPasswordResult:
    try:
        input_model = input.to_pydantic()
    except pydantic.ValidationError as exc:
        return ResetPasswordValidationError.from_validation_error(exc)

    try:
示例#26
0
@strawberry.input
class AddOpenPositionInput:
    company_name: str
    position: OpenPositionInput

@strawberry.type
class AddOpenPositionCreated:
    company: Company

@strawberry.type
class AddOpenPositionFailed:
    error: str
    suggestion: str = "Fix the error and retry"


AddOpenPositionResult = strawberry.union("AddOpenPositionResult", (AddOpenPositionCreated, AddOpenPositionFailed),
                                         description="Union of possible outcomes when adding a position to a company")


class MutationsEnabled(BasePermission):
    message = "You are not authorized"

    def has_permission(self, source, info, **kwargs):
        return os.getenv('MUTATIONS_ENABLED', False)


ADD_OPEN_POSITION_EXAMPLE = """
```
mutation AgregarBusqueda {
  addOpenPosition(input: {
      companyName: "CompanyName", 
      position: {title: "Architect", url: "www.url.com"}
示例#27
0
@strawberry.type
class CheckoutSession:
    stripe_session_id: str

    @classmethod
    def from_domain(cls, stripe_session_id: str) -> CheckoutSession:
        return cls(
            stripe_session_id=stripe_session_id,
        )


SubscribeUserResult = strawberry.union(
    "SubscribeUserResult",
    (
        CheckoutSession,
        AlreadySubscribed,
    ),
)


@strawberry.mutation(permission_classes=[IsAuthenticated])
async def subscribe_user_to_association(
    info: Info[Context, Any]
) -> SubscribeUserResult:
    try:
        checkout_session_id = await service_subscribe_user_to_association(
            info.context.request.user,
            association_repository=info.context.association_repository,
        )
        return CheckoutSession.from_domain(checkout_session_id)