Coverage for src/lexigram/graphql/schema/types.py: 81%
79 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
1"""GraphQL type helpers.
3This module provides type aliases and helpers for defining
4GraphQL types using Strawberry.
5"""
7from __future__ import annotations
9from typing import Any, Generic, TypeVar, cast
11import strawberry
13T = TypeVar("T")
16# Re-export Strawberry type decorators for convenience
17ObjectType: Any = strawberry.type
18InputType: Any = strawberry.input
19InterfaceType: Any = strawberry.interface
20EnumType: Any = strawberry.enum
21ScalarType: Any = strawberry.scalar
24def union_type(*types: Any, name: str | None = None) -> Any:
25 """Create a GraphQL union type.
27 Args:
28 types: Types to include in the union.
29 name: Optional name for the union.
31 Returns:
32 Union type annotation.
34 Example:
35 ```python
36 SearchResult = union_type(User, Post, Comment, name="SearchResult")
38 @strawberry.type
39 class Query:
40 @strawberry.field
41 def search(self, query: str) -> list[SearchResult]:
42 ...
43 ```
44 """
45 return strawberry.union(name or "Union", types=types) # type: ignore[call-arg]
48# Generic Connection class - NOT a Strawberry type, just a type hint
49# Use create_connection_type() to create concrete Connection types
50class Connection(Generic[T]):
51 """GraphQL Connection type template for cursor-based pagination.
53 This is a generic template. Use create_connection_type() to create
54 concrete Connection types for your node types.
56 Attributes:
57 edges: List of edges containing nodes.
58 page_info: Pagination information.
59 total_count: Total number of items.
60 """
62 class Edge:
63 """Connection edge template."""
65 node: Any
66 cursor: str
68 class PageInfo:
69 """Pagination information."""
71 has_next_page: bool
72 has_previous_page: bool
73 start_cursor: str | None
74 end_cursor: str | None
76 edges: list[Any]
77 page_info: Any # Will be PageInfo type
78 total_count: int = 0
81# Concrete PageInfo type for use in typed connections
82@strawberry.type
83class ConnectionPageInfo:
84 """Pagination information for connections."""
86 has_next_page: bool
87 has_previous_page: bool
88 start_cursor: str | None = None
89 end_cursor: str | None = None
92@strawberry.type
93class PagedResult(Generic[T]):
94 """Simple offset-based pagination result.
96 Attributes:
97 items: List of items in the current page.
98 total: Total number of items.
99 page: Current page number (1-indexed).
100 page_size: Number of items per page.
101 has_next: Whether there are more pages.
102 has_previous: Whether there are previous pages.
103 """
105 items: list[T]
106 total: int
107 page: int
108 page_size: int
109 has_next: bool = False
110 has_previous: bool = False
112 @property
113 def total_pages(self) -> int:
114 """Calculate total number of pages."""
115 if self.page_size <= 0:
116 return 0
117 return (self.total + self.page_size - 1) // self.page_size
120@strawberry.input
121class PaginationInput:
122 """Input for pagination parameters.
124 Attributes:
125 page: Page number (1-indexed).
126 page_size: Number of items per page.
127 """
129 page: int = 1
130 page_size: int = 20
133@strawberry.input
134class CursorPaginationInput:
135 """Input for cursor-based pagination.
137 Attributes:
138 first: Number of items to fetch from the start.
139 after: Cursor to fetch items after.
140 last: Number of items to fetch from the end.
141 before: Cursor to fetch items before.
142 """
144 first: int | None = None
145 after: str | None = None
146 last: int | None = None
147 before: str | None = None
150@strawberry.input
151class SortInput:
152 """Input for sorting.
154 Attributes:
155 field: Field to sort by.
156 direction: Sort direction (ASC or DESC).
157 """
159 field: str
160 direction: str = "ASC"
163@strawberry.type
164class MutationResult(Generic[T]):
165 """Standard mutation result type.
167 Attributes:
168 success: Whether the mutation succeeded.
169 data: Result data if successful.
170 errors: Error messages if failed.
171 """
173 success: bool
174 data: T | None = None
175 errors: list[str] = strawberry.field(default_factory=list)
178@strawberry.type
179class DeleteResult:
180 """Result of a delete mutation.
182 Attributes:
183 success: Whether deletion succeeded.
184 id: ID of deleted item.
185 message: Optional message.
186 """
188 success: bool
189 id: str | None = None
190 message: str | None = None
193def create_connection_type(
194 node_type: type[T],
195 name: str | None = None,
196) -> type[Connection[T]]:
197 """Create a Connection type for a specific node type.
199 Args:
200 node_type: The node type for the connection.
201 name: Optional name prefix for the connection type.
203 Returns:
204 A Connection type class.
205 """
206 type_name = name or node_type.__name__
208 @strawberry.type(name=f"{type_name}Edge")
209 class TypedEdge:
210 node: Any # node_type used at runtime; annotate as Any for typing
211 cursor: str
213 @strawberry.type(name=f"{type_name}Connection")
214 class TypedConnection:
215 edges: list[TypedEdge]
216 page_info: ConnectionPageInfo
217 total_count: int = 0
219 # type so callers and mypy treat this as Connection[T].
220 return cast("type[Connection[T]]", TypedConnection)
223__all__ = [
224 "Connection",
225 "ConnectionPageInfo",
226 "CursorPaginationInput",
227 "DeleteResult",
228 "EnumType",
229 "InputType",
230 "InterfaceType",
231 "MutationResult",
232 "ObjectType",
233 "PagedResult",
234 "PaginationInput",
235 "ScalarType",
236 "SortInput",
237 "create_connection_type",
238 "union_type",
239]