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
114 changes: 59 additions & 55 deletions README.md

Large diffs are not rendered by default.

40 changes: 19 additions & 21 deletions bookshop/app_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
from bookshop.graphql.runtime import _operation_type
from bookshop.graphql.runtime import _reflection
from bookshop.graphql import runtime as _runtime
from bookshop.graphql.schema import enum as _enum
from bookshop.graphql.schema import input as _input
from bookshop.graphql import _scalar
from bookshop.graphql import schema as _schema

class _BookCard_author(_compat.TypedDict, closed=True):
name: _builtins.str
Expand All @@ -17,23 +15,23 @@ class BookCard(_compat.TypedDict):
"""What a book shows in a list."""
id: _builtins.str
title: _builtins.str
price: _scalar.Money
price: _schema.Money
author: _BookCard_author | None
"""Null for an anthology, which has no single author."""

class GetBookVariables(_compat.TypedDict, closed=True):
lookup: _input.BookLookup
lookup: _schema.BookLookup
"""An identifier or an ISBN."""
withReviews: _typing.NotRequired[_builtins.bool]

class _GetBookData_book_reviews(_compat.TypedDict, closed=True):
rating: _builtins.int
"""From 1 to 5."""
text: _builtins.str | None
postedAt: _scalar.DateTime
postedAt: _schema.DateTime

class _GetBookData_book(BookCard, closed=True):
isbn: _scalar.ISBN
isbn: _schema.ISBN
pageCount: _builtins.int
reviews: _typing.NotRequired[_builtins.list[_GetBookData_book_reviews]]

Expand Down Expand Up @@ -71,19 +69,19 @@ class SearchVariables(_compat.TypedDict, closed=True):

class _SearchData_search_Book(_SearchData_search_Book_Base, closed=True):
title: _builtins.str
price: _scalar.Money
price: _schema.Money

_SearchData_search_Magazine_Base = _compat.TypedDict('_SearchData_search_Magazine_Base', {'__typename': _compat.ReadOnly[_typing.Literal['Magazine']]})

class _SearchData_search_Magazine(_SearchData_search_Magazine_Base, closed=True):
title: _builtins.str
price: _scalar.Money
price: _schema.Money

_SearchData_search_Audiobook_Base = _compat.TypedDict('_SearchData_search_Audiobook_Base', {'__typename': _compat.ReadOnly[_typing.Literal['Audiobook']]})

class _SearchData_search_Audiobook(_SearchData_search_Audiobook_Base, closed=True):
title: _builtins.str
price: _scalar.Money
price: _schema.Money

_SearchData_search_Author_Base = _compat.TypedDict('_SearchData_search_Author_Base', {'__typename': _compat.ReadOnly[_typing.Literal['Author']]})

Expand All @@ -110,11 +108,11 @@ class SearchData(_compat.TypedDict, closed=True):
}""", variables_type=SearchVariables, data_type=SearchData)

class ListBooksVariables(_compat.TypedDict, closed=True):
filter: _typing.NotRequired[_input.BookFilter | None]
filter: _typing.NotRequired[_schema.BookFilter | None]
first: _typing.NotRequired[_builtins.int | None]

class _ListBooksData_books(BookCard, closed=True):
genre: _enum.Genre
genre: _schema.Genre

class ListBooksData(_compat.TypedDict, closed=True):
books: _builtins.list[_ListBooksData_books]
Expand Down Expand Up @@ -144,7 +142,7 @@ class GetPublicationVariables(_compat.TypedDict, closed=True):
class _GetPublicationData_publication_Book(_GetPublicationData_publication_Book_Base, closed=True):
title: _builtins.str
pages: _builtins.int
isbn: _scalar.ISBN
isbn: _schema.ISBN

_GetPublicationData_publication_Magazine_Base = _compat.TypedDict('_GetPublicationData_publication_Magazine_Base', {'__typename': _compat.ReadOnly[_typing.Literal['Magazine']]})

Expand Down Expand Up @@ -186,7 +184,7 @@ class GetSavedSearchVariables(_compat.TypedDict, closed=True):
name: _builtins.str

class _GetSavedSearchData_savedSearch(_compat.TypedDict, closed=True):
value: _input.BookFilter | None
value: _schema.BookFilter | None

class GetSavedSearchData(_compat.TypedDict, closed=True):
savedSearch: _GetSavedSearchData_savedSearch | None
Expand All @@ -199,13 +197,13 @@ class GetSavedSearchData(_compat.TypedDict, closed=True):
}""", variables_type=GetSavedSearchVariables, data_type=GetSavedSearchData)

class PlaceOrderVariables(_compat.TypedDict, closed=True):
input: _input.PlaceOrderInput
input: _schema.PlaceOrderInput

class _PlaceOrderData_placeOrder(_compat.TypedDict, closed=True):
id: _builtins.str
status: _enum.OrderStatus
total: _scalar.Money
placedAt: _scalar.DateTime
status: _schema.OrderStatus
total: _schema.Money
placedAt: _schema.DateTime

class PlaceOrderData(_compat.TypedDict, closed=True):
placeOrder: _PlaceOrderData_placeOrder
Expand All @@ -221,11 +219,11 @@ class PlaceOrderData(_compat.TypedDict, closed=True):
}""", variables_type=PlaceOrderVariables, injections={'idempotencyKey': _builtins.frozenset({('input',)})}, data_type=PlaceOrderData)

class CancelOrderVariables(_compat.TypedDict, closed=True):
input: _input.CancelOrderInput
input: _schema.CancelOrderInput

class _CancelOrderData_cancelOrder(_compat.TypedDict, closed=True):
id: _builtins.str
status: _enum.OrderStatus
status: _schema.OrderStatus

class CancelOrderData(_compat.TypedDict, closed=True):
cancelOrder: _CancelOrderData_cancelOrder | None
Expand All @@ -246,7 +244,7 @@ class OnOrderStatusChangedVariables(_compat.TypedDict, closed=True):

class _OnOrderStatusChangedData_orderStatusChanged(_compat.TypedDict, closed=True):
id: _builtins.str
status: _enum.OrderStatus
status: _schema.OrderStatus

class OnOrderStatusChangedData(_compat.TypedDict, closed=True):
orderStatusChanged: _OnOrderStatusChangedData_orderStatusChanged
Expand Down
4 changes: 2 additions & 2 deletions bookshop/get_order_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from bookshop.graphql.runtime import _compat
from bookshop.graphql.runtime import _operation_type
from bookshop.graphql import runtime as _runtime
from bookshop.graphql.schema import enum as _enum
from bookshop.graphql import schema as _schema

class GetOrderVariables(_compat.TypedDict, closed=True):
id: _builtins.str

class _GetOrderData_order(_compat.TypedDict, closed=True):
id: _builtins.str
status: _enum.OrderStatus
status: _schema.OrderStatus

class GetOrderData(_compat.TypedDict, closed=True):
order: _GetOrderData_order | None
Expand Down
2 changes: 1 addition & 1 deletion bookshop/graphql.config.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# An SDL file here, but globs, introspection results, and server URLs work too.
schema: schema.graphqls
documents: "*.graphql"
# This library's config, under its extension name.
# graphql-codegen's config, under its extension name.
extensions:
pythonCodegen:
# Where imports start, relative to this file's directory.
Expand Down
19 changes: 0 additions & 19 deletions bookshop/graphql/_scalar.py

This file was deleted.

4 changes: 2 additions & 2 deletions bookshop/graphql/injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import collections.abc as _abc
from .runtime import _compat
from .runtime import injection as _injection
from . import _scalar
from . import schema as _schema

class InjectorFunctions(_compat.TypedDict, closed=True):
"""The functions supplying each injected value, by name.

Where the value may be null, one returning `OMITTED` leaves it out, and one returning `None` sends `null`."""
idempotencyKey: _abc.Callable[[], _scalar.UUID]
idempotencyKey: _abc.Callable[[], _schema.UUID]

def injectors(functions: InjectorFunctions, /) -> _injection._Injectors:
"""Return what `Request.prepare()` calls to supply the injected values, each serialized as its type says."""
Expand Down
4 changes: 2 additions & 2 deletions bookshop/graphql/runtime/_reflection.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def convert(value: object, /) -> object:
try:
result[key] = convert_value(result[key])
except UnexpectedNullError as error:
error.path.insert(0, key)
error._path.insert(0, key)
raise

return result
Expand Down Expand Up @@ -323,7 +323,7 @@ def convert(value: object, /) -> object:
try:
result.append(convert_item(item))
except UnexpectedNullError as error:
error.path.insert(0, index)
error._path.insert(0, index)
raise

return result
Expand Down
14 changes: 11 additions & 3 deletions bookshop/graphql/runtime/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ class UnexpectedNullError(Error):
"""

def __init__(self, path: list[str | int], /) -> None:
self.path: Final = path
"""Private."""
# Each level the error unwinds through prepends its key or index.
self._path: Final = path
super().__init__()

@property
def path(self) -> Sequence[str | int]:
return self._path

@override
def __str__(self) -> str:
formatted = "".join(
f"[{segment}]" if isinstance(segment, int) else f".{segment}"
for segment in self.path
for segment in self._path
).removeprefix(".")
return f"Expected `{formatted}` to not be null."

Expand All @@ -51,7 +57,7 @@ class GraphqlError(TypedDict):
message: Required[str]
locations: NotRequired[Sequence[Location]]
path: NotRequired[Sequence[str | int]]
extensions: NotRequired[object]
extensions: NotRequired[Mapping[str, object]]


class ResponseError(Error):
Expand All @@ -64,6 +70,7 @@ def __init__(
*,
extensions: Mapping[str, object] | None,
) -> None:
"""Private."""
assert errors, "Cannot raise without at least one error."
self.errors: Final = tuple(errors)
self.extensions: Final = extensions
Expand Down Expand Up @@ -117,6 +124,7 @@ def __init__(
extensions: Mapping[str, object] | None,
partial_data_parser: Callable[[object], object] | None,
) -> None:
"""Private."""
super().__init__(errors, extensions=extensions)
self.data = data
self._partial_data_parser: Final = partial_data_parser
5 changes: 1 addition & 4 deletions bookshop/graphql/runtime/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ def __init__(
injections: Mapping[str, frozenset[tuple[str, ...]]] = _NO_INJECTIONS,
data_type: type[Data],
) -> None:
"""*injections* maps each injector's name to the paths of the objects receiving its value, `()` being the variables themselves.

What crosses the wire is read off *variables_type* and *data_type* at runtime.
"""
"""Private."""
self._operation_type: Final = operation_type
self._name: Final = name
self._document: Final = document
Expand Down
45 changes: 30 additions & 15 deletions bookshop/graphql/schema/input.py → bookshop/graphql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,53 @@
import builtins as _builtins
import collections.abc as _abc
import typing as _typing
from ..runtime import _compat
from ..schema import enum as _enum
from .. import _scalar
from .runtime import _compat
from .runtime import _reflection
from datetime import datetime as _datetime
from ..scalar import decode_datetime as _decode_datetime
from ..scalar import encode_datetime as _encode_datetime
from ..scalar import ISBN as _ISBN
from decimal import Decimal as _Decimal
from uuid import UUID as _UUID

type _BookFilter_Ref = BookFilter
type DateTime = _typing.Annotated[_datetime, _reflection.Codec(decode=_decode_datetime, encode=_encode_datetime)]

type Money = _typing.Annotated[_Decimal, _reflection.Codec(decode=_Decimal, encode=_builtins.str)]

type ISBN = _ISBN

type UUID = _typing.Annotated[_UUID, _reflection.Codec(decode=_UUID, encode=_builtins.str)]

type Genre = _typing.Literal['FICTION', 'NONFICTION', 'POETRY', 'CHILDREN']

type OrderStatus = _typing.Literal['PENDING', 'SHIPPED', 'DELIVERED', 'CANCELED']

type BookLookup = _BookLookup_id | _BookLookup_isbn
"""A book, found by exactly one of its identifiers."""

class _BookLookup_id(_compat.TypedDict, closed=True):
id: _builtins.str

class _BookLookup_isbn(_compat.TypedDict, closed=True):
isbn: _scalar.ISBN
isbn: ISBN

type BookLookup = _BookLookup_id | _BookLookup_isbn
"""A book, found by exactly one of its identifiers."""
type BookFilter = _BookFilter_genre | _BookFilter_author | _BookFilter_priceBelow | _BookFilter_and | _BookFilter_or | _BookFilter_not
"""A recursive filter on books."""

class _BookFilter_genre(_compat.TypedDict, closed=True):
genre: _enum.Genre
genre: Genre

class _BookFilter_author(_compat.TypedDict, closed=True):
author: _builtins.str

class _BookFilter_priceBelow(_compat.TypedDict, closed=True):
priceBelow: _scalar.Money
priceBelow: Money

_BookFilter_and = _compat.TypedDict('_BookFilter_and', {'and': _abc.Sequence[_BookFilter_Ref]}, closed=True)
_BookFilter_and = _compat.TypedDict('_BookFilter_and', {'and': _abc.Sequence[BookFilter]}, closed=True)

_BookFilter_or = _compat.TypedDict('_BookFilter_or', {'or': _abc.Sequence[_BookFilter_Ref]}, closed=True)
_BookFilter_or = _compat.TypedDict('_BookFilter_or', {'or': _abc.Sequence[BookFilter]}, closed=True)

_BookFilter_not = _compat.TypedDict('_BookFilter_not', {'not': _BookFilter_Ref}, closed=True)

type BookFilter = _BookFilter_genre | _BookFilter_author | _BookFilter_priceBelow | _BookFilter_and | _BookFilter_or | _BookFilter_not
"""A recursive filter on books."""
_BookFilter_not = _compat.TypedDict('_BookFilter_not', {'not': BookFilter}, closed=True)

class PlaceOrderInput(_compat.TypedDict, closed=True):
lines: _abc.Sequence[OrderLineInput]
Expand Down
4 changes: 0 additions & 4 deletions bookshop/graphql/schema/__init__.py

This file was deleted.

7 changes: 0 additions & 7 deletions bookshop/graphql/schema/enum.py

This file was deleted.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires = ["uv-build>=0.12.23,<0.14"]
[project]
authors = [{ name = "Thibault Derousseaux" }]
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3 :: Only",
# The versions CI tests.
Expand Down Expand Up @@ -35,7 +35,7 @@ license-files = ["LICENSE"]
name = "graphql-codegen"
readme = "README.md"
requires-python = ">=3.12"
version = "0.2.1"
version = "1.0.0"

[project.optional-dependencies]
# Only reading a YAML graphql-config file needs it.
Expand Down
5 changes: 4 additions & 1 deletion src/graphql_codegen/_cli/_graphql_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ def _parse_extension(
default=None,
)
injector_names = optional(
unread, "injectorNames", parse_strings, default=frozenset()
unread,
"injectorNames",
lambda value: set(parse_strings(value)),
default=set(),
)
non_null_directive_name = optional(
unread, "nonNullDirectiveName", parse_string, default=None
Expand Down
Loading