Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix specialized connection aliases missing filters/ordering #547

Merged
merged 3 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ django = ">=3.2"
asgiref = ">=3.8"
django-choices-field = { version = ">=2.2.2", optional = true }
django-debug-toolbar = { version = ">=3.4", optional = true }
strawberry-graphql = ">=0.227.1"
strawberry-graphql = ">=0.234.2"

[tool.poetry.group.dev.dependencies]
Markdown = "^3.3.7"
Expand Down
15 changes: 12 additions & 3 deletions strawberry_django/fields/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import functools
from typing import TYPE_CHECKING, Any, Optional, TypeVar
from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast

from django.db.models import ForeignKey
from strawberry import LazyType, relay
Expand All @@ -17,6 +17,7 @@
get_object_definition,
)
from strawberry.union import StrawberryUnion
from strawberry.utils.inspect import get_specialized_type_var_map

from strawberry_django.resolvers import django_resolver
from strawberry_django.utils.typing import (
Expand Down Expand Up @@ -80,16 +81,24 @@ def is_async(self) -> bool:
def django_type(self) -> type[WithStrawberryDjangoObjectDefinition] | None:
origin = self.type

if isinstance(origin, LazyType):
bellini666 marked this conversation as resolved.
Show resolved Hide resolved
origin = origin.resolve_type()

object_definition = get_object_definition(origin)

if object_definition and issubclass(object_definition.origin, relay.Connection):
origin = object_definition.type_var_map.get("NodeType")
origin_specialized_type_var_map = (
bellini666 marked this conversation as resolved.
Show resolved Hide resolved
get_specialized_type_var_map(cast(type, origin)) or {}
)
origin = origin_specialized_type_var_map.get("NodeType")
bellini666 marked this conversation as resolved.
Show resolved Hide resolved

if origin is None:
bellini666 marked this conversation as resolved.
Show resolved Hide resolved
origin = object_definition.type_var_map.get("NodeType")

if origin is None:
specialized_type_var_map = (
object_definition.specialized_type_var_map or {}
)

origin = specialized_type_var_map["NodeType"]

if isinstance(origin, LazyType):
Expand Down
Empty file added tests/relay/lazy/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions tests/relay/lazy/a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import TYPE_CHECKING

import strawberry
from strawberry import relay
from typing_extensions import Annotated, TypeAlias

import strawberry_django
from strawberry_django.relay import ListConnectionWithTotalCount

from .models import RelayAuthor

if TYPE_CHECKING:
from .b import BookConnection


@strawberry_django.type(RelayAuthor)
class AuthorType(relay.Node):
name: str
books: Annotated["BookConnection", strawberry.lazy("tests.relay.lazy.b")] = (
strawberry_django.connection()
)


AuthorConnection: TypeAlias = ListConnectionWithTotalCount[AuthorType]
bellini666 marked this conversation as resolved.
Show resolved Hide resolved
32 changes: 32 additions & 0 deletions tests/relay/lazy/b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import TYPE_CHECKING

import strawberry
from strawberry import relay
from typing_extensions import Annotated, TypeAlias

import strawberry_django
from strawberry_django.relay import ListConnectionWithTotalCount

from .models import RelayBook

if TYPE_CHECKING:
from .a import AuthorType


@strawberry_django.filter(RelayBook)
class BookFilter:
name: str


@strawberry_django.order(RelayBook)
class BookOrder:
name: str


@strawberry_django.type(RelayBook, filters=BookFilter, order=BookOrder)
class BookType(relay.Node):
name: str
author: Annotated["AuthorType", strawberry.lazy("tests.relay.lazy.a")]


BookConnection: TypeAlias = ListConnectionWithTotalCount[BookType]
bellini666 marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 14 additions & 0 deletions tests/relay/lazy/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.db import models


class RelayAuthor(models.Model):
name = models.CharField(max_length=100)


class RelayBook(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(
RelayAuthor,
on_delete=models.CASCADE,
related_name="books",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
type AuthorType implements Node {
"""The Globally Unique ID of this object"""
id: GlobalID!
name: String!
books(
filters: BookFilter
order: BookOrder

"""Returns the items in the list that come before the specified cursor."""
before: String = null

"""Returns the items in the list that come after the specified cursor."""
after: String = null

"""Returns the first n items from the list."""
first: Int = null

"""Returns the items in the list that come after the specified cursor."""
last: Int = null
): BookTypeConnection!
}

"""A connection to a list of items."""
type AuthorTypeConnection {
"""Pagination data for this connection"""
pageInfo: PageInfo!

"""Contains the nodes in this connection"""
edges: [AuthorTypeEdge!]!

"""Total quantity of existing nodes."""
totalCount: Int
}

"""An edge in a connection."""
type AuthorTypeEdge {
"""A cursor for use in pagination"""
cursor: String!

"""The item at the end of the edge"""
node: AuthorType!
}

input BookFilter {
name: String!
AND: BookFilter
OR: BookFilter
NOT: BookFilter
DISTINCT: Boolean
}

input BookOrder {
name: String
}

type BookType implements Node {
"""The Globally Unique ID of this object"""
id: GlobalID!
name: String!
author: AuthorType!
}

"""A connection to a list of items."""
type BookTypeConnection {
"""Pagination data for this connection"""
pageInfo: PageInfo!

"""Contains the nodes in this connection"""
edges: [BookTypeEdge!]!

"""Total quantity of existing nodes."""
totalCount: Int
}

"""An edge in a connection."""
type BookTypeEdge {
"""A cursor for use in pagination"""
cursor: String!

"""The item at the end of the edge"""
node: BookType!
}

"""
The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.
"""
scalar GlobalID @specifiedBy(url: "https://relay.dev/graphql/objectidentification.htm")

"""An object with a Globally Unique ID"""
interface Node {
"""The Globally Unique ID of this object"""
id: GlobalID!
}

"""Information to aid in pagination."""
type PageInfo {
"""When paginating forwards, are there more items?"""
hasNextPage: Boolean!

"""When paginating backwards, are there more items?"""
hasPreviousPage: Boolean!

"""When paginating backwards, the cursor to continue."""
startCursor: String

"""When paginating forwards, the cursor to continue."""
endCursor: String
}

type Query {
booksConn(
filters: BookFilter
order: BookOrder

"""Returns the items in the list that come before the specified cursor."""
before: String = null

"""Returns the items in the list that come after the specified cursor."""
after: String = null

"""Returns the first n items from the list."""
first: Int = null

"""Returns the items in the list that come after the specified cursor."""
last: Int = null
): BookTypeConnection!
booksConn2(
filters: BookFilter
order: BookOrder

"""Returns the items in the list that come before the specified cursor."""
before: String = null

"""Returns the items in the list that come after the specified cursor."""
after: String = null

"""Returns the first n items from the list."""
first: Int = null

"""Returns the items in the list that come after the specified cursor."""
last: Int = null
): BookTypeConnection!
authorsConn(
"""Returns the items in the list that come before the specified cursor."""
before: String = null

"""Returns the items in the list that come after the specified cursor."""
after: String = null

"""Returns the first n items from the list."""
first: Int = null

"""Returns the items in the list that come after the specified cursor."""
last: Int = null
): AuthorTypeConnection!
authorsConn2(
"""Returns the items in the list that come before the specified cursor."""
before: String = null

"""Returns the items in the list that come after the specified cursor."""
after: String = null

"""Returns the first n items from the list."""
first: Int = null

"""Returns the items in the list that come after the specified cursor."""
last: Int = null
): AuthorTypeConnection!
}
28 changes: 28 additions & 0 deletions tests/relay/lazy/test_lazy_annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pathlib

import strawberry
from pytest_snapshot.plugin import Snapshot

import strawberry_django
from strawberry_django.relay import ListConnectionWithTotalCount

from .a import AuthorConnection, AuthorType
from .b import BookConnection, BookType

SNAPSHOTS_DIR = pathlib.Path(__file__).parent / "snapshots"


def test_lazy_type_annotations_in_schema(snapshot: Snapshot):
@strawberry.type
bellini666 marked this conversation as resolved.
Show resolved Hide resolved
class Query:
books_conn: BookConnection = strawberry_django.connection()
books_conn2: ListConnectionWithTotalCount[BookType] = (
strawberry_django.connection()
)
authors_conn: AuthorConnection = strawberry_django.connection()
authors_conn2: ListConnectionWithTotalCount[AuthorType] = (
strawberry_django.connection()
)

schema = strawberry.Schema(query=Query)
snapshot.assert_match(str(schema), "authors_and_books_schema.gql")
bellini666 marked this conversation as resolved.
Show resolved Hide resolved
Loading