Skip to content

Commit 2a89581

Browse files
committed
refactor: allow ruff to update Optional/Union usage to | syntax
1 parent ce81bae commit 2a89581

File tree

253 files changed

+2333
-2592
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

253 files changed

+2333
-2592
lines changed

RELEASE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Release type: minor
22

3-
This release drops support for Python 3.9, which reached its end-of-life (EOL)
4-
in October 2025. The minimum supported Python version is now 3.10.
3+
This release drops updates our strawberry's codebase to use the "|" syntax for
4+
Optional and Union types, which was introduced in Python 3.10.
55

6-
We strongly recommend upgrading to Python 3.10 or a newer version, as older
7-
versions are no longer maintained and may contain security vulnerabilities.
6+
This change improves code readability and aligns with modern Python practices,
7+
and should not affect existing functionality.

federation-compatibility/schema.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,13 @@ class Custom: ...
121121
@strawberry.federation.type(extend=True, keys=["email"])
122122
class User:
123123
email: strawberry.ID = strawberry.federation.field(external=True)
124-
name: Optional[str] = strawberry.federation.field(override="users")
125-
total_products_created: Optional[int] = strawberry.federation.field(external=True)
124+
name: str | None = strawberry.federation.field(override="users")
125+
total_products_created: int | None = strawberry.federation.field(external=True)
126126
years_of_employment: int = strawberry.federation.field(external=True)
127127

128128
# TODO: the camel casing will be fixed in a future release of Strawberry
129129
@strawberry.federation.field(requires=["totalProductsCreated", "yearsOfEmployment"])
130-
def average_products_created_per_year(self) -> Optional[int]:
130+
def average_products_created_per_year(self) -> int | None:
131131
if self.total_products_created is not None:
132132
return round(self.total_products_created / self.years_of_employment)
133133

@@ -150,9 +150,9 @@ def resolve_reference(cls, **data: Any) -> Optional["User"]:
150150

151151
@strawberry.federation.type(shareable=True)
152152
class ProductDimension:
153-
size: Optional[str]
154-
weight: Optional[float]
155-
unit: Optional[str] = strawberry.federation.field(inaccessible=True)
153+
size: str | None
154+
weight: float | None
155+
unit: str | None = strawberry.federation.field(inaccessible=True)
156156

157157

158158
@strawberry.type
@@ -163,13 +163,13 @@ class ProductVariation:
163163
@strawberry.type
164164
class CaseStudy:
165165
case_number: strawberry.ID
166-
description: Optional[str]
166+
description: str | None
167167

168168

169169
@strawberry.federation.type(keys=["study { caseNumber }"])
170170
class ProductResearch:
171171
study: CaseStudy
172-
outcome: Optional[str]
172+
outcome: str | None
173173

174174
@classmethod
175175
def from_data(cls, data: dict) -> "ProductResearch":
@@ -206,8 +206,8 @@ def resolve_reference(cls, **data: Any) -> Optional["ProductResearch"]:
206206
class DeprecatedProduct:
207207
sku: str
208208
package: str
209-
reason: Optional[str]
210-
created_by: Optional[User]
209+
reason: str | None
210+
created_by: User | None
211211

212212
@classmethod
213213
def resolve_reference(cls, **data: Any) -> Optional["DeprecatedProduct"]:
@@ -231,27 +231,27 @@ def resolve_reference(cls, **data: Any) -> Optional["DeprecatedProduct"]:
231231
)
232232
class Product:
233233
id: strawberry.ID
234-
sku: Optional[str]
235-
package: Optional[str]
234+
sku: str | None
235+
package: str | None
236236
variation_id: strawberry.Private[str]
237237

238238
@strawberry.field
239-
def variation(self) -> Optional[ProductVariation]:
239+
def variation(self) -> ProductVariation | None:
240240
return (
241241
ProductVariation(strawberry.ID(self.variation_id))
242242
if self.variation_id
243243
else None
244244
)
245245

246246
@strawberry.field
247-
def dimensions(self) -> Optional[ProductDimension]:
247+
def dimensions(self) -> ProductDimension | None:
248248
return ProductDimension(**dimension)
249249

250250
@strawberry.federation.field(provides=["totalProductsCreated"])
251-
def created_by(self) -> Optional[User]:
251+
def created_by(self) -> User | None:
252252
return User(**user)
253253

254-
notes: Optional[str] = strawberry.federation.field(tags=["internal"])
254+
notes: str | None = strawberry.federation.field(tags=["internal"])
255255
research: list[ProductResearch]
256256

257257
@classmethod
@@ -301,10 +301,10 @@ def resolve_reference(cls, id: strawberry.ID) -> "Inventory":
301301

302302
@strawberry.federation.type(extend=True)
303303
class Query:
304-
product: Optional[Product] = strawberry.field(resolver=get_product_by_id)
304+
product: Product | None = strawberry.field(resolver=get_product_by_id)
305305

306306
@strawberry.field(deprecation_reason="Use product query instead")
307-
def deprecated_product(self, sku: str, package: str) -> Optional[DeprecatedProduct]:
307+
def deprecated_product(self, sku: str, package: str) -> DeprecatedProduct | None:
308308
return None
309309

310310

pyproject.toml

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,6 @@ src = ["strawberry", "tests"]
225225
[tool.ruff.lint]
226226
select = ["ALL"]
227227
ignore = [
228-
# https://github.com/astral-sh/ruff/pull/4427
229-
# equivalent to keep-runtime-typing. We might want to enable those
230-
# after we drop support for Python 3.9
231-
"UP006",
232-
"UP007",
233-
"UP045",
234-
235228
# we use asserts in tests and to hint mypy
236229
"S101",
237230

@@ -339,6 +332,9 @@ ignore = [
339332
"W191",
340333
"PLR0915",
341334
]
335+
exclude = [
336+
"tests/python_312/*",
337+
] # SyntaxError because of syntax only added at 3.12
342338

343339
[tool.ruff.lint.per-file-ignores]
344340
".github/*" = ["INP001"]

strawberry/aiohttp/test/client.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import (
55
TYPE_CHECKING,
66
Any,
7-
Optional,
87
)
98

109
from strawberry.test.client import BaseGraphQLTestClient, Response
@@ -17,11 +16,11 @@ class GraphQLTestClient(BaseGraphQLTestClient):
1716
async def query(
1817
self,
1918
query: str,
20-
variables: Optional[dict[str, Mapping]] = None,
21-
headers: Optional[dict[str, object]] = None,
22-
asserts_errors: Optional[bool] = None,
23-
files: Optional[dict[str, object]] = None,
24-
assert_no_errors: Optional[bool] = True,
19+
variables: dict[str, Mapping] | None = None,
20+
headers: dict[str, object] | None = None,
21+
asserts_errors: bool | None = None,
22+
files: dict[str, object] | None = None,
23+
assert_no_errors: bool | None = True,
2524
) -> Response:
2625
body = self._build_body(query, variables, files)
2726

@@ -54,8 +53,8 @@ async def query(
5453
async def request(
5554
self,
5655
body: dict[str, object],
57-
headers: Optional[dict[str, object]] = None,
58-
files: Optional[dict[str, object]] = None,
56+
headers: dict[str, object] | None = None,
57+
files: dict[str, object] | None = None,
5958
) -> Any:
6059
return await self._client.post(
6160
self.url,

strawberry/aiohttp/views.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
from json.decoder import JSONDecodeError
77
from typing import (
88
TYPE_CHECKING,
9-
Optional,
109
TypeGuard,
11-
Union,
1210
)
1311

1412
from lia import AiohttpHTTPRequestAdapter, HTTPException
@@ -72,7 +70,7 @@ async def close(self, code: int, reason: str) -> None:
7270
class GraphQLView(
7371
AsyncBaseHTTPView[
7472
web.Request,
75-
Union[web.Response, web.StreamResponse],
73+
web.Response | web.StreamResponse,
7674
web.Response,
7775
web.Request,
7876
web.WebSocketResponse,
@@ -91,8 +89,8 @@ class GraphQLView(
9189
def __init__(
9290
self,
9391
schema: BaseSchema,
94-
graphiql: Optional[bool] = None,
95-
graphql_ide: Optional[GraphQL_IDE] = "graphiql",
92+
graphiql: bool | None = None,
93+
graphql_ide: GraphQL_IDE | None = "graphiql",
9694
allow_queries_via_get: bool = True,
9795
keep_alive: bool = True,
9896
keep_alive_interval: float = 1,
@@ -131,12 +129,12 @@ def is_websocket_request(self, request: web.Request) -> TypeGuard[web.Request]:
131129
ws = web.WebSocketResponse(protocols=self.subscription_protocols)
132130
return ws.can_prepare(request).ok
133131

134-
async def pick_websocket_subprotocol(self, request: web.Request) -> Optional[str]:
132+
async def pick_websocket_subprotocol(self, request: web.Request) -> str | None:
135133
ws = web.WebSocketResponse(protocols=self.subscription_protocols)
136134
return ws.can_prepare(request).protocol
137135

138136
async def create_websocket_response(
139-
self, request: web.Request, subprotocol: Optional[str]
137+
self, request: web.Request, subprotocol: str | None
140138
) -> web.WebSocketResponse:
141139
protocols = [subprotocol] if subprotocol else []
142140
ws = web.WebSocketResponse(protocols=protocols)
@@ -152,17 +150,17 @@ async def __call__(self, request: web.Request) -> web.StreamResponse:
152150
status=e.status_code,
153151
)
154152

155-
async def get_root_value(self, request: web.Request) -> Optional[RootValue]:
153+
async def get_root_value(self, request: web.Request) -> RootValue | None:
156154
return None
157155

158156
async def get_context(
159-
self, request: web.Request, response: Union[web.Response, web.WebSocketResponse]
157+
self, request: web.Request, response: web.Response | web.WebSocketResponse
160158
) -> Context:
161159
return {"request": request, "response": response} # type: ignore
162160

163161
def create_response(
164162
self,
165-
response_data: Union[GraphQLHTTPResponse, list[GraphQLHTTPResponse]],
163+
response_data: GraphQLHTTPResponse | list[GraphQLHTTPResponse],
166164
sub_response: web.Response,
167165
) -> web.Response:
168166
sub_response.text = self.encode_json(response_data)

strawberry/annotation.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
Annotated,
1212
Any,
1313
ForwardRef,
14-
Optional,
1514
TypeVar,
16-
Union,
1715
cast,
1816
get_args,
1917
get_origin,
@@ -61,14 +59,14 @@ class StrawberryAnnotation:
6159

6260
def __init__(
6361
self,
64-
annotation: Union[object, str],
62+
annotation: object | str,
6563
*,
66-
namespace: Optional[dict[str, Any]] = None,
64+
namespace: dict[str, Any] | None = None,
6765
) -> None:
6866
self.raw_annotation = annotation
6967
self.namespace = namespace
7068

71-
self.__resolve_cache__: Optional[Union[StrawberryType, type]] = None
69+
self.__resolve_cache__: StrawberryType | type | None = None
7270

7371
def __eq__(self, other: object) -> bool:
7472
if not isinstance(other, StrawberryAnnotation):
@@ -81,8 +79,8 @@ def __hash__(self) -> int:
8179

8280
@staticmethod
8381
def from_annotation(
84-
annotation: object, namespace: Optional[dict[str, Any]] = None
85-
) -> Optional[StrawberryAnnotation]:
82+
annotation: object, namespace: dict[str, Any] | None = None
83+
) -> StrawberryAnnotation | None:
8684
if annotation is None:
8785
return None
8886

@@ -91,7 +89,7 @@ def from_annotation(
9189
return annotation
9290

9391
@property
94-
def annotation(self) -> Union[object, str]:
92+
def annotation(self) -> object | str:
9593
"""Return evaluated type on success or fallback to raw (string) annotation."""
9694
try:
9795
return self.evaluate()
@@ -101,7 +99,7 @@ def annotation(self) -> Union[object, str]:
10199
return self.raw_annotation
102100

103101
@annotation.setter
104-
def annotation(self, value: Union[object, str]) -> None:
102+
def annotation(self, value: object | str) -> None:
105103
self.raw_annotation = value
106104

107105
self.__resolve_cache__ = None
@@ -131,8 +129,8 @@ def _get_type_with_args(
131129
def resolve(
132130
self,
133131
*,
134-
type_definition: Optional[StrawberryObjectDefinition] = None,
135-
) -> Union[StrawberryType, type]:
132+
type_definition: StrawberryObjectDefinition | None = None,
133+
) -> StrawberryType | type:
136134
"""Return resolved (transformed) annotation."""
137135
if (resolved := self.__resolve_cache__) is None:
138136
resolved = self._resolve()
@@ -161,11 +159,11 @@ def resolve(
161159

162160
return resolved
163161

164-
def _resolve(self) -> Union[StrawberryType, type]:
162+
def _resolve(self) -> StrawberryType | type:
165163
evaled_type = cast("Any", self.evaluate())
166164
return self._resolve_evaled_type(evaled_type)
167165

168-
def _resolve_evaled_type(self, evaled_type: Any) -> Union[StrawberryType, type]:
166+
def _resolve_evaled_type(self, evaled_type: Any) -> StrawberryType | type:
169167
if is_private(evaled_type):
170168
return evaled_type
171169

@@ -247,7 +245,7 @@ def create_optional(self, evaled_type: Any) -> StrawberryOptional:
247245
# passed as we can safely use `Union` for both optional types
248246
# (e.g. `Optional[str]`) and optional unions (e.g.
249247
# `Optional[Union[TypeA, TypeB]]`)
250-
child_type = Union[non_optional_types] # type: ignore
248+
child_type = non_optional_types # type: ignore
251249

252250
of_type = StrawberryAnnotation(
253251
annotation=child_type,
@@ -324,7 +322,7 @@ def _is_enum(cls, annotation: Any) -> bool:
324322
return issubclass(annotation, Enum)
325323

326324
@classmethod
327-
def _is_type_generic(cls, type_: Union[StrawberryType, type]) -> bool:
325+
def _is_type_generic(cls, type_: StrawberryType | type) -> bool:
328326
"""Returns True if `resolver_type` is generic else False."""
329327
from strawberry.types.base import StrawberryType
330328

0 commit comments

Comments
 (0)