Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions bookshop/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type Query {
"The publication with this identifier."
publication(id: ID!): Publication

"The printed publication with this identifier."
printed(id: ID!): Printed

"Everything whose title or name contains the text."
search(text: String!): [SearchResult!]!

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ license-files = ["LICENSE"]
name = "graphql-codegen"
readme = "README.md"
requires-python = ">=3.12"
version = "1.0.1"
version = "1.0.2"

[project.optional-dependencies]
# Only reading a YAML graphql-config file needs it.
Expand Down
2 changes: 1 addition & 1 deletion src/graphql_codegen/_generator/_data_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def _emit_normalized(
if field.name == "__typename":
keys.append(
Key(
name="__typename",
name=field.response_name,
annotation=_typename_annotation(
self._possible_types(normalized.type)
),
Expand Down
2 changes: 1 addition & 1 deletion src/graphql_codegen/_generator/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ def _annotations(self) -> AnnotationBuilder:

def _module_source(
self,
body: list[ast.stmt],
body: Sequence[ast.stmt],
/,
*,
generated_module: GeneratedModule,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_bookshop.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import runpy
from asyncio import run as run_async
from collections.abc import Callable, Mapping
from collections.abc import Callable, Collection, Mapping
from decimal import Decimal
from http.server import BaseHTTPRequestHandler
from pathlib import Path
Expand Down Expand Up @@ -171,7 +171,7 @@ def test_a_request_error_is_read_whatever_its_status(
],
)
def test_watching_an_order_asynchronously_follows_its_statuses(
events: list[str], statuses: list[str], mocked_http: Callable[[_Handle], None]
events: Collection[str], statuses: list[str], mocked_http: Callable[[_Handle], None]
) -> None:
mocked_http(lambda _request: _events(*events))

Expand Down
3 changes: 2 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import shutil
import subprocess
import sys
from collections.abc import Sequence
from pathlib import Path

import pytest
Expand All @@ -20,7 +21,7 @@
],
)
def test_the_cli_generates_the_committed_bookshop_package(
command: list[str], tmp_path: Path
command: Sequence[str], tmp_path: Path
) -> None:
generated = (BOOKSHOP_GENERATED_PACKAGE_DIRECTORY.name, "*_graphql.py")

Expand Down
117 changes: 59 additions & 58 deletions tests/test_emit.py

Large diffs are not rendered by default.

41 changes: 14 additions & 27 deletions tests/test_generate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from collections.abc import Callable
from collections.abc import Callable, Collection, Mapping
from pathlib import PurePosixPath
from typing import Final

Expand Down Expand Up @@ -40,7 +40,7 @@


@pytest.mark.parametrize(
("document", "config", "path", "present", "absent"),
("document", "config", "path", "present"),
[
pytest.param(
_PRICE,
Expand All @@ -51,15 +51,13 @@
"from my_app.money import decode as _decode",
"type Money = _typing.Annotated[_Decimal, _reflection.Codec(decode=_decode, encode=_encode)]",
],
[],
id="a codec scalar imports its paths absolutely, and never while generating: they need not exist yet",
),
pytest.param(
_PRICE,
Config(scalars={"ISBN": Scalar(type="str")}),
"schema.py",
["type ISBN = _builtins.str"],
[],
id="an identity scalar named by a bare name is a builtin",
),
pytest.param(
Expand All @@ -78,63 +76,60 @@
"from .. import decode_money as _decode_money",
"from ...money import encode as _encode",
],
[],
id="a path starting with two dots or more is relative to the generated package's directory",
),
pytest.param(
_PRICE,
Config(),
"document/GraphQL_request.py",
["price: _builtins.object"],
[],
id="a custom scalar left out of the config is typed `object`",
),
pytest.param(
_SAVED_SEARCH,
Config(struct_interface_name="Struct"),
"document/GraphQL_request.py",
["value: _schema.BookFilter | None"],
[],
id="a struct payload is typed with its input type",
),
pytest.param(
_SAVED_SEARCH,
Config(struct_interface_name="Struct"),
"schema.py",
["type BookFilter = "],
["PlaceOrderInput"],
id="which it reaches even when no variable does",
),
pytest.param(
_SAVED_SEARCH,
Config(),
"document/GraphQL_request.py",
["value: _builtins.object | None"],
[],
id="but only when the struct interface is named",
),
pytest.param(
'query Q { savedSearch(name: "classics") { value @nonNull } }',
Config(non_null_directive_name="nonNull", struct_interface_name="Struct"),
"document/GraphQL_request.py",
["value: _typing.Annotated[_schema.BookFilter, _reflection.NON_NULL]"],
[],
id="and not optional when asserted non-null",
),
pytest.param(
'query Q { book(lookup: {id: "1"}) @nonNull { title } }',
Config(non_null_directive_name="nonNull"),
"document/GraphQL_request.py",
["book: _typing.Annotated[_QData_book, _reflection.NON_NULL]"],
["@nonNull"],
[
"book: _typing.Annotated[_QData_book, _reflection.NON_NULL]",
'document="""query Q {\n book§: book(lookup: { id: "1" }) {\n title\n }\n}"""',
],
id="a field asserted non-null is not optional, and its directive stays off the wire",
),
pytest.param(
_PLACE_ORDER,
Config(injector_names={"idempotencyKey"}),
"schema.py",
["class PlaceOrderInput("],
["idempotencyKey"],
[
'class PlaceOrderInput(_compat.TypedDict, closed=True):\n customer: _typing.NotRequired[_builtins.str | None]\n """The customer to act for, the authenticated account itself when omitted."""\n lines: _abc.Sequence[OrderLineInput]\n'
],
id="an injected field is left out of its input type",
),
pytest.param(
Expand All @@ -144,7 +139,6 @@
[
"injections={'city': _builtins.frozenset({('input', 'shippingAddress')})}"
],
[],
id="and received wherever an input holds it, at any depth",
),
pytest.param(
Expand All @@ -155,15 +149,13 @@
"note: _abc.Callable[[], _builtins.str | None | _OMITTED]",
"def injectors(functions: InjectorFunctions, /) -> _injection._Injectors:",
],
[],
id="an injector is required, typed by what it injects, and may return `OMITTED` where it is nullable",
),
pytest.param(
f"{_PLACE_ORDER} query N($note: String!) {{ savedSearch(name: $note) {{ value }} }}",
Config(injector_names={"note"}),
"injection.py",
["note: _abc.Callable[[], _builtins.str]"],
[],
id="an injector takes the strictest nullability of its positions, since it cannot tell which one it is called for",
),
pytest.param(
Expand All @@ -173,7 +165,6 @@
[
"InjectorFunctions = _compat.TypedDict('InjectorFunctions', {'in': _abc.Callable[[], _builtins.str]}, closed=True)"
],
[],
id="an injector named like a keyword is declared functionally",
),
],
Expand All @@ -182,8 +173,7 @@ def test_a_config_field_shapes_the_package(
document: str,
config: Config,
path: str,
present: list[str],
absent: list[str],
present: Collection[str],
bookshop_schema: GraphQLSchema,
) -> None:
source = generate(document=parse(document), schema=bookshop_schema, config=config)[
Expand All @@ -193,9 +183,6 @@ def test_a_config_field_shapes_the_package(
for snippet in present:
assert snippet in source

for snippet in absent:
assert snippet not in source


def _message(error: Exception, /) -> str:
return error.message if isinstance(error, GraphQLError) else str(error)
Expand Down Expand Up @@ -358,7 +345,7 @@ def _message(error: Exception, /) -> str:
],
)
def test_an_input_is_rejected(
schema: str, document: str, config: Config, errors: list[Exception]
schema: str, document: str, config: Config, errors: Collection[Exception]
) -> None:
with pytest.raises((ExceptionGroup, GraphQLError, ValueError)) as error_info:
generate(document=parse(document), schema=build_schema(schema), config=config)
Expand Down Expand Up @@ -399,7 +386,7 @@ def test_a_config_field_cannot_be_constructed_invalid(
construct()


def _documents(sources: dict[str, str], /) -> DocumentNode:
def _documents(sources: Mapping[str, str], /) -> DocumentNode:
return DocumentNode(
definitions=tuple(
definition
Expand All @@ -410,7 +397,7 @@ def _documents(sources: dict[str, str], /) -> DocumentNode:


def _generated(
sources: dict[str, str],
sources: Mapping[str, str],
/,
*,
sibling_module_name: str | None,
Expand Down Expand Up @@ -523,7 +510,7 @@ def test_a_fragment_goes_next_to_the_operations_of_its_document_and_is_imported_
],
)
def test_a_document_s_module_depends_on_its_document_alone(
sources: dict[str, str],
sources: Mapping[str, str],
sibling_module_name: str | None,
message: str,
bookshop_schema: GraphQLSchema,
Expand Down
5 changes: 4 additions & 1 deletion tests/test_merge.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from collections.abc import Collection
from itertools import product
from typing import Any

Expand Down Expand Up @@ -172,7 +173,9 @@ def test_a_lone_operation_is_sent_without_its_sigils(
),
],
)
def test_an_operation_merged_with_itself(source: str, snippets: list[str]) -> None:
def test_an_operation_merged_with_itself(
source: str, snippets: Collection[str]
) -> None:
operation = _operation(source)
query = _sent(merge(operation({}), operation({})))["query"]

Expand Down
4 changes: 2 additions & 2 deletions tests/test_runtime.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from collections.abc import Callable
from collections.abc import Callable, Mapping
from typing import Annotated, Any, Final, Literal, assert_type

import pytest
Expand Down Expand Up @@ -171,7 +171,7 @@ def _sent(


def _mutation(
document: str, /, *, injections: dict[str, frozenset[tuple[str, ...]]]
document: str, /, *, injections: Mapping[str, frozenset[tuple[str, ...]]]
) -> Operation[Literal["mutation"], dict[str, object], dict[str, object]]:
return Operation(
operation_type="mutation",
Expand Down
Loading