Skip to content

Commit

Permalink
add interface support (restricted to 1 interface in this implementation)
Browse files Browse the repository at this point in the history
  • Loading branch information
m.kindritskiy committed Jul 29, 2023
1 parent 6eea610 commit 7dad476
Show file tree
Hide file tree
Showing 19 changed files with 996 additions and 71 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ RUN pdm sync -G docs

FROM base as tests

RUN pdm sync -G test
RUN pdm sync -G test -G dev
RUN python3 -m pip install tox tox-pdm

FROM base as examples
Expand Down
13 changes: 6 additions & 7 deletions hiku/denormalize/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typing as t
from collections import deque

from ..graph import Graph, Union
from ..graph import Graph, Interface, Union
from ..query import (
QueryVisitor,
Link,
Expand All @@ -12,10 +12,9 @@
from ..types import (
Record,
RecordMeta,
TypeRefMeta,
RefMeta,
OptionalMeta,
SequenceMeta,
UnionRefMeta,
get_type,
)

Expand All @@ -25,7 +24,7 @@ def __init__(self, graph: Graph, result: Proxy) -> None:
self._types = graph.__types__
self._unions = graph.unions_map
self._result = result
self._type: t.Deque[t.Union[t.Type[Record], Union]] = deque(
self._type: t.Deque[t.Union[t.Type[Record], Union, Interface]] = deque(
[self._types["__root__"]]
)
self._data = deque([result])
Expand All @@ -49,7 +48,7 @@ def visit_link(self, obj: Link) -> None:
else:
raise AssertionError(repr(self._type[-1]))

if isinstance(type_, (TypeRefMeta, UnionRefMeta)):
if isinstance(type_, RefMeta):
self._type.append(get_type(self._types, type_))
self._res.append({})
self._data.append(self._data[-1][obj.result_key])
Expand All @@ -61,7 +60,7 @@ def visit_link(self, obj: Link) -> None:
type_ref = type_.__item_type__
if isinstance(type_.__item_type__, OptionalMeta):
type_ref = type_.__item_type__.__type__
assert isinstance(type_ref, (TypeRefMeta, UnionRefMeta))
assert isinstance(type_ref, RefMeta)
self._type.append(get_type(self._types, type_ref))
items = []
for item in self._data[-1][obj.result_key]:
Expand All @@ -76,7 +75,7 @@ def visit_link(self, obj: Link) -> None:
if self._data[-1][obj.result_key] is None:
self._res[-1][obj.result_key] = None
else:
assert isinstance(type_.__type__, (TypeRefMeta, UnionRefMeta))
assert isinstance(type_.__type__, RefMeta)
self._type.append(get_type(self._types, type_.__type__))
self._res.append({})
self._data.append(self._data[-1][obj.result_key])
Expand Down
15 changes: 7 additions & 8 deletions hiku/denormalize/graphql.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
from collections import deque

from ..graph import Graph, Union
from ..graph import Graph, Interface, Union
from ..query import Field, Link
from ..result import Proxy
from ..types import (
RecordMeta,
TypeRefMeta,
RefMeta,
SequenceMeta,
OptionalMeta,
GenericMeta,
UnionRefMeta,
)

from .base import Denormalize
Expand All @@ -25,11 +24,11 @@ def __init__(
def visit_field(self, obj: Field) -> None:
if obj.name == "__typename":
type_name = self._type_name[-1]
if isinstance(self._type[-1], Union):
if isinstance(self._type[-1], (Union, Interface)):
type_name = self._data[-1].__ref__.node
self._res[-1][obj.result_key] = type_name
else:
if isinstance(self._type[-1], Union):
if isinstance(self._type[-1], (Union, Interface)):
type_name = self._data[-1].__ref__.node

if obj.name not in self._types[type_name].__field_types__:
Expand All @@ -46,16 +45,16 @@ def visit_link(self, obj: Link) -> None:
raise AssertionError(repr(self._type[-1]))

type_ref: GenericMeta
if isinstance(type_, (TypeRefMeta, UnionRefMeta)):
if isinstance(type_, RefMeta):
type_ref = type_
elif isinstance(type_, SequenceMeta):
type_ref = type_.__item_type__
if isinstance(type_ref, OptionalMeta):
type_ref = type_ref.__type__
assert isinstance(type_ref, (TypeRefMeta, UnionRefMeta)), type_ref
assert isinstance(type_ref, RefMeta), type_ref
elif isinstance(type_, OptionalMeta):
type_ref = type_.__type__
assert isinstance(type_ref, (TypeRefMeta, UnionRefMeta)), type_ref
assert isinstance(type_ref, RefMeta), type_ref
else:
raise AssertionError(repr(type_))
self._type_name.append(type_ref.__type_name__)
Expand Down
52 changes: 41 additions & 11 deletions hiku/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
)
from .compat import Concatenate, ParamSpec
from .executors.base import SyncAsyncExecutor
from .interface import SplitInterfaceQueryByNodes
from .query import (
Node as QueryNode,
Field as QueryField,
Expand All @@ -40,6 +41,7 @@
QueryVisitor,
)
from .graph import (
Interface,
Link,
Maybe,
MaybeMany,
Expand All @@ -63,7 +65,7 @@
TaskSet,
SubmitRes,
)
from .union import SplitUnionByNodes
from .union import SplitUnionQueryByNodes
from .utils import ImmutableDict

if TYPE_CHECKING:
Expand Down Expand Up @@ -103,24 +105,32 @@ def __init__(self, graph: Graph) -> None:
@contextlib.contextmanager
def enter_path(self, type_: Any) -> Iterator[None]:
try:
self._path.append(type_)
if type_ is not None:
self._path.append(type_)
yield
finally:
self._path.pop()
if type_ is not None:
self._path.pop()

def visit_node(self, obj: QueryNode) -> QueryNode:
fields = []
is_union = isinstance(self._path[-1], GraphUnion)
is_interface = isinstance(self._path[-1], Interface)

for f in obj.fields:
if f.name == "__typename":
fields.append(f)
continue

enter_path = self.enter_path if is_union else contextlib.nullcontext
type_ = self._graph.nodes_map[f.parent_type] if is_union else None
type_ = None

if is_union:
type_ = self._graph.nodes_map[f.parent_type]
elif is_interface:
if f.parent_type is not None:
type_ = self._graph.nodes_map[f.parent_type]

with enter_path(type_): # type: ignore[operator]
with self.enter_path(type_):
fields.append(self.visit(f))

return obj.copy(fields=fields)
Expand All @@ -138,6 +148,8 @@ def visit_link(self, obj: QueryLink) -> QueryLink:
if isinstance(graph_obj, Link):
if graph_obj.is_union:
self._path.append(self._graph.unions_map[graph_obj.node])
elif graph_obj.is_interface:
self._path.append(self._graph.interfaces_map[graph_obj.node])
else:
self._path.append(self._graph.nodes_map[graph_obj.node])
try:
Expand Down Expand Up @@ -358,29 +370,29 @@ def link_ref_maybe(graph_link: Link, ident: Any) -> Optional[Reference]:
if ident is Nothing:
return None
else:
if graph_link.is_union:
if graph_link.is_union or graph_link.is_interface:
return Reference(ident[1].__type_name__, ident[0])
return Reference(graph_link.node, ident)


def link_ref_one(graph_link: Link, ident: Any) -> Reference:
assert ident is not Nothing

if graph_link.is_union:
if graph_link.is_union or graph_link.is_interface:
return Reference(ident[1].__type_name__, ident[0])
return Reference(graph_link.node, ident)


def link_ref_many(graph_link: Link, idents: List) -> List[Reference]:
if graph_link.is_union:
if graph_link.is_union or graph_link.is_interface:
return [Reference(i[1].__type_name__, i[0]) for i in idents]
return [Reference(graph_link.node, i) for i in idents]


def link_ref_maybe_many(
graph_link: Link, idents: List
) -> List[Optional[Reference]]:
if graph_link.is_union:
if graph_link.is_union or graph_link.is_interface:
return [
Reference(i[1].__type_name__, i[0]) if i is not Nothing else None
for i in idents
Expand Down Expand Up @@ -718,7 +730,7 @@ def process_link(
# FIXME: call track len(ids) - 1 times because first track was
# already called by process_node for this link
track_times = len(grouped_ids) - 1
union_nodes = SplitUnionByNodes(
union_nodes = SplitUnionQueryByNodes(
self._graph, self._graph.unions_map[graph_link.node]
).split(query_link.node)
for type_name, type_ids in grouped_ids.items():
Expand All @@ -730,7 +742,25 @@ def process_link(
)
for _ in range(track_times):
self._track(path)
elif graph_link.is_interface and isinstance(to_ids, list):
grouped_ids = defaultdict(list)
for id_, type_ref in to_ids:
grouped_ids[type_ref.__type_name__].append(id_)

track_times = len(grouped_ids) - 1

interface_nodes = SplitInterfaceQueryByNodes(self._graph).split(
query_link.node
)
for type_name, type_ids in grouped_ids.items():
self.process_node(
path,
self._graph.nodes_map[type_name],
interface_nodes[type_name],
list(type_ids),
)
for _ in range(track_times):
self._track(path)
else:
if graph_link.type_enum is MaybeMany:
to_ids = [id_ for id_ in to_ids if id_ is not Nothing]
Expand Down
4 changes: 3 additions & 1 deletion hiku/federation/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Field,
Graph as _Graph,
GraphTransformer,
Interface,
Link,
Node,
Option,
Expand Down Expand Up @@ -130,6 +131,7 @@ def __init__(
data_types: t.Optional[t.Dict[str, t.Type[Record]]] = None,
directives: t.Optional[t.Sequence[t.Type[SchemaDirective]]] = None,
unions: t.Optional[t.List[Union]] = None,
interfaces: t.Optional[t.List[Interface]] = None,
is_async: bool = False,
):
if unions is None:
Expand All @@ -141,4 +143,4 @@ def __init__(

items = GraphInit.init(items, is_async, bool(entity_types))

super().__init__(items, data_types, directives, unions)
super().__init__(items, data_types, directives, unions, interfaces)
2 changes: 2 additions & 0 deletions hiku/federation/sdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ def skip(node: Node) -> bool:
obj.data_types,
obj.directives,
obj.unions,
obj.interfaces,
)

def visit_node(self, obj: Node) -> Node:
Expand All @@ -509,6 +510,7 @@ def skip(field: t.Union[Field, Link]) -> bool:
fields=[self.visit(f) for f in obj.fields if not skip(f)],
description=obj.description,
directives=obj.directives,
implements=obj.implements,
)


Expand Down
Loading

0 comments on commit 7dad476

Please sign in to comment.