Coverage for src/lexigram/graphql/core/context/_models.py: 98%
43 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 request/response/error domain models."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from datetime import UTC, datetime
7from typing import TYPE_CHECKING, Any, Generic, TypeVar
8import uuid
10from lexigram.contracts.auth import AuthenticatorProtocol
11from lexigram.contracts.core import IdGeneratorProtocol
12from lexigram.contracts.graphql import (
13 GraphQLPrincipal,
14 GraphQLPrincipalResolverProtocol,
15)
16from lexigram.domain import DomainModel
17from lexigram.logging import get_logger
18from lexigram.primitives import clock as ambient_clock
19from lexigram.validation import Field
21if TYPE_CHECKING:
22 from lexigram.graphql.config import GraphQLConfig
24logger = get_logger(__name__)
27T = TypeVar("T")
30@dataclass(init=False)
31class GraphQLRequest(DomainModel):
32 """GraphQL request model.
34 Represents an incoming GraphQL request with query,
35 variables, and operation name.
37 Attributes:
38 query: The GraphQL query string.
39 variables: Variables for the query.
40 operation_name: Name of the operation to execute.
41 extensions: Optional extensions data.
42 """
44 query: str = Field(..., description="GraphQL query string")
45 variables: dict[str, Any] = Field(
46 default_factory=dict,
47 description="Query variables",
48 )
49 operation_name: str | None = Field(
50 default=None,
51 description="Operation name to execute",
52 )
53 extensions: dict[str, Any] = Field(
54 default_factory=dict,
55 description="Request extensions",
56 )
58 model_config = {"frozen": False}
61@dataclass(init=False)
62class GraphQLErrorPayload(DomainModel):
63 """GraphQL error payload model.
65 G-09 FIX: Renamed from GraphQLError to GraphQLErrorPayload to avoid
66 naming collision with the GraphQLError in exceptions.py.
68 Represents a GraphQL error following the spec.
70 Attributes:
71 message: Human-readable error message.
72 locations: Source locations of the error.
73 path: Path to the field that caused the error.
74 extensions: Additional error information.
75 """
77 message: str = Field(..., description="Error message")
78 locations: list[dict[str, int]] | None = Field(
79 default=None,
80 description="Source locations",
81 )
82 path: list[str | int] | None = Field(
83 default=None,
84 description="Path to the error",
85 )
86 extensions: dict[str, Any] = Field(
87 default_factory=dict,
88 description="Error extensions",
89 )
92@dataclass(init=False)
93class GraphQLResponse(DomainModel, Generic[T]):
94 """GraphQL response model.
96 Represents a GraphQL response with data and/or errors.
98 Attributes:
99 data: The result data.
100 errors: List of errors if any occurred.
101 extensions: Optional response extensions.
102 """
104 data: T | None = Field(default=None, description="Response data")
105 errors: list[GraphQLErrorPayload] | None = Field(
106 default=None,
107 description="List of errors",
108 )
109 extensions: dict[str, Any] = Field(
110 default_factory=dict,
111 description="Response extensions",
112 )
113 http_headers: dict[str, str] = Field(
114 default_factory=dict,
115 description="HTTP response headers (e.g. Cache-Control, Vary) set by the executor",
116 )
118 @property
119 def has_errors(self) -> bool:
120 """Check if response has errors."""
121 return self.errors is not None and len(self.errors) > 0
123 @property
124 def is_successful(self) -> bool:
125 """Check if response is successful."""
126 return not self.has_errors
128 def add_error(
129 self,
130 message: str,
131 path: list[str | int] | None = None,
132 extensions: dict[str, Any] | None = None,
133 ) -> None:
134 """Add an error to the response.
136 Args:
137 message: Error message.
138 path: Path to the error.
139 extensions: Additional error data.
140 """
141 if self.errors is None:
142 self.errors = []
144 self.errors.append(
145 GraphQLErrorPayload(
146 message=message,
147 path=path,
148 extensions=extensions or {},
149 ),
150 )