diff --git a/swh/graphql/app.py b/swh/graphql/app.py --- a/swh/graphql/app.py +++ b/swh/graphql/app.py @@ -26,15 +26,19 @@ resolvers.visit_status, resolvers.snapshot, resolvers.snapshot_branch, + resolvers.branch_target, + resolvers.branch_target_node, resolvers.revision, resolvers.release, + resolvers.release_target, + resolvers.release_target_node, resolvers.directory, resolvers.directory_entry, - resolvers.search_result, - resolvers.branch_target, - resolvers.release_target, resolvers.directory_entry_target, + resolvers.directory_entry_target_node, + resolvers.search_result, resolvers.search_result_target, + resolvers.search_result_target_node, resolvers.binary_string, resolvers.date, scalars.id_scalar, diff --git a/swh/graphql/resolvers/content.py b/swh/graphql/resolvers/content.py --- a/swh/graphql/resolvers/content.py +++ b/swh/graphql/resolvers/content.py @@ -3,7 +3,7 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional from swh.graphql.errors import DataError, InvalidInputError from swh.model import hashutil @@ -11,10 +11,7 @@ from .base_connection import BaseList from .base_node import BaseSWHNode -from .directory_entry import BaseDirectoryEntryNode -from .release import BaseReleaseNode -from .search import SearchResultNode -from .snapshot_branch import BaseSnapshotBranchNode +from .target import TargetNode def read_and_validate_content_hashes(hashes) -> Dict[str, bytes]: @@ -94,17 +91,12 @@ """ _can_be_null = True - obj: Union[ - SearchResultNode, - BaseDirectoryEntryNode, - BaseReleaseNode, - BaseSnapshotBranchNode, - ] + obj: TargetNode def _get_node_data(self) -> Optional[Content]: # FIXME, this is not considering hash collisions # and could return a wrong object in very rare situations - contents = self.archive.get_contents(hashes={"sha1_git": self.obj.target_hash}) + contents = self.archive.get_contents(hashes={"sha1_git": self.obj.target_id}) # always returning the first content from the storage return contents[0] if contents else None diff --git a/swh/graphql/resolvers/directory.py b/swh/graphql/resolvers/directory.py --- a/swh/graphql/resolvers/directory.py +++ b/swh/graphql/resolvers/directory.py @@ -3,16 +3,12 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Union - from swh.model.model import Directory from swh.model.swhids import ObjectType from .base_node import BaseSWHNode -from .release import BaseReleaseNode from .revision import BaseRevisionNode -from .search import SearchResultNode -from .snapshot_branch import BaseSnapshotBranchNode +from .target import TargetNode class BaseDirectoryNode(BaseSWHNode): @@ -65,15 +61,8 @@ Node resolver for a directory requested as a target """ - from .directory_entry import BaseDirectoryEntryNode - _can_be_null = True - obj: Union[ - BaseSnapshotBranchNode, - BaseReleaseNode, - BaseDirectoryEntryNode, - SearchResultNode, - ] + obj: TargetNode def _get_node_data(self): - return self._get_directory_by_id(self.obj.target_hash) + return self._get_directory_by_id(self.obj.target_id) diff --git a/swh/graphql/resolvers/directory_entry.py b/swh/graphql/resolvers/directory_entry.py --- a/swh/graphql/resolvers/directory_entry.py +++ b/swh/graphql/resolvers/directory_entry.py @@ -12,11 +12,11 @@ class BaseDirectoryEntryNode(BaseNode): @property - def target_hash(self): # for DirectoryNode + def target_id(self): return self._node.target @property - def targetType(self): # To support the schema naming convention + def target_type(self): mapping = {"file": "content", "dir": "directory", "rev": "revision"} return mapping.get(self._node.type) diff --git a/swh/graphql/resolvers/origin.py b/swh/graphql/resolvers/origin.py --- a/swh/graphql/resolvers/origin.py +++ b/swh/graphql/resolvers/origin.py @@ -8,7 +8,7 @@ from .base_connection import BaseConnection from .base_node import BaseSWHNode -from .search import SearchResultNode +from .target import TargetNode class BaseOriginNode(BaseSWHNode): @@ -32,13 +32,13 @@ Node resolver for an origin requested as a target """ - obj: SearchResultNode + obj: TargetNode def _get_node_data(self): - # The target origin URL is guaranteed to exist in the archive + # The target origin URL (target_id here) is guaranteed to exist in the archive # Hence returning the origin object without any explicit check in the archive # This assumes that the search index and archive are in sync - return Origin(self.obj.target_url) + return Origin(self.obj.target_id) class OriginConnection(BaseConnection): diff --git a/swh/graphql/resolvers/release.py b/swh/graphql/resolvers/release.py --- a/swh/graphql/resolvers/release.py +++ b/swh/graphql/resolvers/release.py @@ -3,11 +3,8 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Union - from .base_node import BaseSWHNode -from .search import SearchResultNode -from .snapshot_branch import BaseSnapshotBranchNode +from .target import TargetNode class BaseReleaseNode(BaseSWHNode): @@ -19,11 +16,11 @@ return self.archive.get_releases([release_id])[0] @property - def target_hash(self): + def target_id(self): return self._node.target @property - def targetType(self): # To support the schema naming convention + def target_type(self): return self._node.target_type.value def is_type_of(self): @@ -47,8 +44,7 @@ """ _can_be_null = True - obj: Union[BaseSnapshotBranchNode, BaseReleaseNode, SearchResultNode] + obj: TargetNode def _get_node_data(self): - # self.obj.target_hash is the requested release id - return self._get_release_by_id(self.obj.target_hash) + return self._get_release_by_id(self.obj.target_id) diff --git a/swh/graphql/resolvers/resolver_factory.py b/swh/graphql/resolvers/resolver_factory.py --- a/swh/graphql/resolvers/resolver_factory.py +++ b/swh/graphql/resolvers/resolver_factory.py @@ -33,7 +33,8 @@ TargetSnapshotNode, VisitSnapshotNode, ) -from .snapshot_branch import AliasSnapshotBranchNode, SnapshotBranchConnection +from .snapshot_branch import SnapshotBranchConnection, TargetSnapshotBranchNode +from .target import TargetNode from .visit import LatestVisitNode, OriginVisitConnection, OriginVisitNode from .visit_status import LatestVisitStatusNode, VisitStatusConnection @@ -46,31 +47,20 @@ "latest-status": LatestVisitStatusNode, "visit-snapshot": VisitSnapshotNode, "snapshot": SnapshotNode, - "branch-alias": AliasSnapshotBranchNode, - "branch-revision": TargetRevisionNode, - "branch-release": TargetReleaseNode, - "branch-directory": TargetDirectoryNode, - "branch-content": TargetContentNode, - "branch-snapshot": TargetSnapshotNode, "revision": RevisionNode, "revision-directory": RevisionDirectoryNode, "release": ReleaseNode, - "release-revision": TargetRevisionNode, - "release-release": TargetReleaseNode, - "release-directory": TargetDirectoryNode, - "release-content": TargetContentNode, "directory": DirectoryNode, "directory-entry": DirectoryEntryNode, "content-by-hashes": ContentbyHashesNode, - "dir-entry-content": TargetContentNode, - "dir-entry-directory": TargetDirectoryNode, - "dir-entry-revision": TargetRevisionNode, - "search-result-origin": TargetOriginNode, - "search-result-snapshot": TargetSnapshotNode, - "search-result-revision": TargetRevisionNode, - "search-result-release": TargetReleaseNode, - "search-result-directory": TargetDirectoryNode, - "search-result-content": TargetContentNode, + "target": TargetNode, + "target-origin": TargetOriginNode, + "target-snapshot": TargetSnapshotNode, + "target-alias": TargetSnapshotBranchNode, + "target-revision": TargetRevisionNode, + "target-release": TargetReleaseNode, + "target-directory": TargetDirectoryNode, + "target-content": TargetContentNode, } @classmethod diff --git a/swh/graphql/resolvers/resolvers.py b/swh/graphql/resolvers/resolvers.py --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -38,18 +38,22 @@ visit_status: ObjectType = ObjectType("VisitStatus") snapshot: ObjectType = ObjectType("Snapshot") snapshot_branch: ObjectType = ObjectType("Branch") +branch_target: ObjectType = ObjectType("BranchTarget") revision: ObjectType = ObjectType("Revision") release: ObjectType = ObjectType("Release") +release_target: ObjectType = ObjectType("ReleaseTarget") directory: ObjectType = ObjectType("Directory") directory_entry: ObjectType = ObjectType("DirectoryEntry") +directory_entry_target: ObjectType = ObjectType("DirectoryEntryTarget") search_result: ObjectType = ObjectType("SearchResult") +search_result_target: ObjectType = ObjectType("SearchResultTarget") binary_string: ObjectType = ObjectType("BinaryString") date: ObjectType = ObjectType("Date") -branch_target: UnionType = UnionType("BranchTarget") -release_target: UnionType = UnionType("ReleaseTarget") -directory_entry_target: UnionType = UnionType("DirectoryEntryTarget") -search_result_target: UnionType = UnionType("SearchResultTarget") +branch_target_node: UnionType = UnionType("BranchTargetNode") +release_target_node: UnionType = UnionType("ReleaseTargetNode") +directory_entry_target_node: UnionType = UnionType("DirectoryEntryTargetNode") +search_result_target_node: UnionType = UnionType("SearchResultTargetNode") # Node resolvers # A node resolver will return either an instance of a BaseNode subclass or None @@ -95,24 +99,6 @@ return NodeObjectFactory.create("visit-snapshot", obj, info, **kw) -@snapshot_branch.field("target") -def snapshot_branch_target_resolver( - obj: rs.snapshot_branch.BaseSnapshotBranchNode, info: GraphQLResolveInfo, **kw -) -> Union[ - rs.revision.BaseRevisionNode, - rs.release.BaseReleaseNode, - rs.directory.BaseDirectoryNode, - rs.content.BaseContentNode, - rs.snapshot.BaseSnapshotNode, - rs.snapshot_branch.BaseSnapshotBranchNode, -]: - """ - Snapshot branch target can be a revision, release, directory, - content, snapshot or a branch itself (alias type) - """ - return NodeObjectFactory.create(f"branch-{obj.targetType}", obj, info, **kw) - - @query.field("revision") def revision_resolver( obj: None, info: GraphQLResolveInfo, **kw @@ -134,21 +120,6 @@ return NodeObjectFactory.create("release", obj, info, **kw) -@release.field("target") -def release_target_resolver( - obj: rs.release.BaseReleaseNode, info: GraphQLResolveInfo, **kw -) -> Union[ - rs.revision.BaseRevisionNode, - rs.release.BaseReleaseNode, - rs.directory.BaseDirectoryNode, - rs.content.BaseContentNode, -]: - """ - Release target can be a release, revision, directory or a content - """ - return NodeObjectFactory.create(f"release-{obj.targetType}", obj, info, **kw) - - @query.field("directory") def directory_resolver( obj: None, info: GraphQLResolveInfo, **kw @@ -163,23 +134,37 @@ return NodeObjectFactory.create("directory-entry", obj, info, **kw) -@directory_entry.field("target") -def directory_entry_target_resolver( - obj: rs.directory_entry.BaseDirectoryEntryNode, info: GraphQLResolveInfo, **kw -) -> Union[ - rs.revision.BaseRevisionNode, - rs.directory.BaseDirectoryNode, - rs.content.BaseContentNode, -]: - """ - DirectoryEntry target can be a directory, content or a revision - """ - return NodeObjectFactory.create(f"dir-entry-{obj.targetType}", obj, info, **kw) +@query.field("contentByHashes") +def content_by_hashes_resolver( + obj: None, info: GraphQLResolveInfo, **kw +) -> rs.content.ContentbyHashesNode: + return NodeObjectFactory.create("content-by-hashes", obj, info, **kw) +@snapshot_branch.field("target") +@release.field("target") +@directory_entry.field("target") @search_result.field("target") -def search_result_target_resolver( - obj: rs.search.SearchResultNode, info: GraphQLResolveInfo, **kw +def generic_target_resolver( + obj: Union[ + rs.snapshot_branch.BaseSnapshotBranchNode, + rs.release.BaseReleaseNode, + rs.directory_entry.BaseDirectoryEntryNode, + rs.search.SearchResultNode, + ], + info: GraphQLResolveInfo, + **kw, +) -> rs.target.TargetNode: + # Generic resolver for all the targets + return NodeObjectFactory.create("target", obj, info, **kw) + + +@branch_target.field("node") +@directory_entry_target.field("node") +@search_result_target.field("node") +@release_target.field("node") +def target_node_resolver( + obj: rs.target.TargetNode, info: GraphQLResolveInfo, **kw ) -> Union[ rs.origin.BaseOriginNode, rs.snapshot.BaseSnapshotNode, @@ -188,18 +173,8 @@ rs.directory.BaseDirectoryNode, rs.content.BaseContentNode, ]: - """ - SearchResult target can be an origin, snapshot, revision, release - directory or a content - """ - return NodeObjectFactory.create(f"search-result-{obj.targetType}", obj, info, **kw) - - -@query.field("contentByHashes") -def content_by_hashes_resolver( - obj: None, info: GraphQLResolveInfo, **kw -) -> rs.content.ContentbyHashesNode: - return NodeObjectFactory.create("content-by-hashes", obj, info, **kw) + # Generic resolver for all the target nodes + return NodeObjectFactory.create(f"target-{obj.type}", obj, info, **kw) # Connection resolvers @@ -317,10 +292,10 @@ # Other resolvers -@release_target.type_resolver -@directory_entry_target.type_resolver -@branch_target.type_resolver -@search_result_target.type_resolver +@release_target_node.type_resolver +@directory_entry_target_node.type_resolver +@search_result_target_node.type_resolver +@branch_target_node.type_resolver def union_resolver( obj: Union[ rs.origin.BaseOriginNode, diff --git a/swh/graphql/resolvers/revision.py b/swh/graphql/resolvers/revision.py --- a/swh/graphql/resolvers/revision.py +++ b/swh/graphql/resolvers/revision.py @@ -3,8 +3,6 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Union - from swh.graphql.utils import utils from swh.model.model import Revision from swh.model.swhids import CoreSWHID, ObjectType @@ -12,10 +10,7 @@ from .base_connection import BaseConnection from .base_node import BaseSWHNode -from .directory_entry import BaseDirectoryEntryNode -from .release import BaseReleaseNode -from .search import SearchResultNode -from .snapshot_branch import BaseSnapshotBranchNode +from .target import TargetNode class BaseRevisionNode(BaseSWHNode): @@ -66,16 +61,10 @@ """ _can_be_null = True - obj: Union[ - BaseSnapshotBranchNode, - BaseReleaseNode, - BaseDirectoryEntryNode, - SearchResultNode, - ] + obj: TargetNode def _get_node_data(self): - # self.obj.target_hash is the requested revision id - return self._get_revision_by_id(self.obj.target_hash) + return self._get_revision_by_id(self.obj.target_id) class ParentRevisionConnection(BaseConnection): diff --git a/swh/graphql/resolvers/scalars.py b/swh/graphql/resolvers/scalars.py --- a/swh/graphql/resolvers/scalars.py +++ b/swh/graphql/resolvers/scalars.py @@ -4,7 +4,7 @@ # See top-level LICENSE file for more information import datetime -from typing import Optional +from typing import Optional, Union from ariadne import ScalarType @@ -19,10 +19,10 @@ @id_scalar.serializer -def serialize_id(value) -> str: - if type(value) is str: - value = value.encode() - return value.hex() +def serialize_id(value: Union[bytes, str]) -> str: + if isinstance(value, bytes): + return value.hex() + return value @datetime_scalar.serializer diff --git a/swh/graphql/resolvers/search.py b/swh/graphql/resolvers/search.py --- a/swh/graphql/resolvers/search.py +++ b/swh/graphql/resolvers/search.py @@ -13,9 +13,13 @@ """ """ @property - def targetType(self): # To support the schema naming convention + def target_type(self): return self._node.type + @property + def target_id(self): + return self._node.target_id + class ResolveSwhidList(BaseList): @@ -28,7 +32,7 @@ if self.archive.is_object_available(swhid.object_id, swhid.object_type): results = [ { - "target_hash": swhid.object_id, + "target_id": swhid.object_id, "type": swhid.object_type.name.lower(), } ] @@ -48,6 +52,6 @@ # FIXME hard coding type to origin for now, as it is the only searchable object results = [ - {"target_url": ori["url"], "type": "origin"} for ori in origins.results + {"target_id": ori["url"], "type": "origin"} for ori in origins.results ] return PagedResult(results=results, next_page_token=origins.next_page_token) diff --git a/swh/graphql/resolvers/snapshot.py b/swh/graphql/resolvers/snapshot.py --- a/swh/graphql/resolvers/snapshot.py +++ b/swh/graphql/resolvers/snapshot.py @@ -3,8 +3,6 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Union - from swh.graphql.errors import NullableObjectError from swh.graphql.utils import utils from swh.model.model import Snapshot @@ -14,7 +12,7 @@ from .base_connection import BaseConnection from .base_node import BaseSWHNode from .origin import OriginNode -from .search import SearchResultNode +from .target import TargetNode from .visit_status import BaseVisitStatusNode @@ -71,13 +69,11 @@ Node resolver for a snapshot requested as a target """ - from .snapshot_branch import BaseSnapshotBranchNode - _can_be_null = True - obj: Union[SearchResultNode, BaseSnapshotBranchNode] + obj: TargetNode def _get_node_data(self): - snapshot_id = self.obj.target_hash + snapshot_id = self.obj.target_id return self._get_snapshot_by_id(snapshot_id) diff --git a/swh/graphql/resolvers/snapshot_branch.py b/swh/graphql/resolvers/snapshot_branch.py --- a/swh/graphql/resolvers/snapshot_branch.py +++ b/swh/graphql/resolvers/snapshot_branch.py @@ -11,13 +11,10 @@ from .base_connection import BaseConnection from .base_node import BaseNode +from .target import TargetNode class BaseSnapshotBranchNode(BaseNode): - - # target field for this node is a UNION type - # It is resolved in the top level (resolvers.resolvers.py) - def _get_node_from_data(self, node_data: tuple): # node_data is a tuple as returned by _get_paged_result in # SnapshotBranchConnection and _get_node_data in AliasSnapshotBranchNode @@ -25,18 +22,14 @@ branch_name, branch_obj = node_data node = { "name": branch_name, - "type": branch_obj.target_type.value, - "target_hash": branch_obj.target, + "target_type": branch_obj.target_type.value, + "target_id": branch_obj.target, } return namedtuple("NodeObj", node.keys())(*node.values()) def is_type_of(self): return "Branch" - @property - def targetType(self): # To support the schema naming convention - return self._node.type - def snapshot_swhid(self): """ Logic to handle multiple branch alias redirects @@ -59,13 +52,13 @@ raise ObjectNotFoundError("There is no snapshot associated with the branch") -class AliasSnapshotBranchNode(BaseSnapshotBranchNode): +class TargetSnapshotBranchNode(BaseSnapshotBranchNode): - obj: BaseSnapshotBranchNode + obj: TargetNode def _get_node_data(self): snapshot_swhid = self.snapshot_swhid() - target_branch = self.obj.target_hash + target_branch = self.obj.target_id alias_branch = self.archive.get_snapshot_branches( snapshot_swhid.object_id, first=1, name_include=target_branch diff --git a/swh/graphql/resolvers/target.py b/swh/graphql/resolvers/target.py new file mode 100644 --- /dev/null +++ b/swh/graphql/resolvers/target.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Union + +from .base_node import BaseNode + +if TYPE_CHECKING: + from .directory_entry import BaseDirectoryEntryNode # pragma: no cover + from .release import BaseReleaseNode # pragma: no cover + from .search import SearchResultNode # pragma: no cover + from .snapshot_branch import BaseSnapshotBranchNode # pragma: no cover + + +class TargetNode(BaseNode): + """ + Generic resolver for all the targets. + Every parent object (self.obj) must have two attributes target_type and target_id. + target_type is exposed as the type attribute and target_id is exposed as the identifier. + target_id is also used to retrieve the node object on request + """ + + # Create a subclass for specific target nodes (say with more fields) + + obj: Union[ + BaseSnapshotBranchNode, + BaseReleaseNode, + BaseDirectoryEntryNode, + SearchResultNode, + ] + + def _get_node_data(self): + # node field of a target object is resolved in the top level resolver + return { + # field exposed in the schema + "type": self.obj.target_type, + # field exposed in the schema, the same value is used to + # retrieve the node object + "identifier": self.obj.target_id, + # field NOT exposed in the schema, used to retrieve the target node + "target_id": self.obj.target_id, + } diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -493,10 +493,21 @@ fullname: BinaryString } + """ -Possible branch target objects +A snapshot branch object """ -union BranchTarget = Revision | Release | Branch | Content | Directory | Snapshot +type Branch { + """ + Branch name + """ + name: BinaryString + + """ + Branch target object + """ + target: BranchTarget +} """ Possible Branch target types @@ -511,23 +522,28 @@ } """ -A snapshot branch object +Possible branch target nodes """ -type Branch { +union BranchTargetNode = Revision | Release | Branch | Content | Directory | Snapshot + +""" +A branch target object +""" +type BranchTarget { """ - Branch name + Branch target type """ - name: BinaryString + type: BranchTargetType! """ - Type of Branch target + Branch target identifier """ - targetType: BranchTargetType + identifier: ID! """ - Branch target object + Branch target node """ - target: BranchTarget + node: BranchTargetNode } """ @@ -632,6 +648,7 @@ """ The unique directory object that revision points to """ + # FIXME, this field could be moved to the target structure if needed directory: Directory """ @@ -666,21 +683,6 @@ ): RevisionConnection } -""" -Possible release target objects -""" -union ReleaseTarget = Release | Revision | Directory | Content - -""" -Possible release target types -""" -enum ReleaseTargetType { - release - revision - content - directory -} - """ A release object """ @@ -716,14 +718,44 @@ date: Date """ - Type of release target + Release target node + """ + target: ReleaseTarget +} + +""" +Possible release target types +""" +enum ReleaseTargetType { + release + revision + content + directory +} + +""" +Possible release target nodes +""" +union ReleaseTargetNode = Release | Revision | Directory | Content + +""" +A release target object +""" +type ReleaseTarget { + """ + Release target type """ - targetType: ReleaseTargetType + type: ReleaseTargetType! """ - Release target object + Release target identifier """ - target: ReleaseTarget + identifier: ID! + + """ + Release target SWH object + """ + node: ReleaseTargetNode } """ @@ -766,10 +798,26 @@ node: DirectoryEntry } + +""" +A directory entry object +""" +type DirectoryEntry { + """ + The directory entry name + """ + name: BinaryString + + """ + Directory entry target node + """ + target: DirectoryEntryTarget +} + """ Possible directory entry target objects """ -union DirectoryEntryTarget = Directory | Content | Revision +union DirectoryEntryTargetNode = Directory | Content | Revision """ Possible directory entry types @@ -781,25 +829,26 @@ } """ -A directory entry object +A directoryentry target object """ -type DirectoryEntry { +type DirectoryEntryTarget { """ - The directory entry name + Directoryentry target type """ - name: BinaryString + type: DirectoryEntryTargetType! """ - Directory entry object type; can be file, dir or rev + Directoryentry target SWHID """ - targetType: DirectoryEntryTargetType + identifier: ID! """ - Directory entry target object + Directoryentry target SWH object """ - target: DirectoryEntryTarget + node: DirectoryEntryTargetNode } + """ A directory object """ @@ -974,8 +1023,20 @@ node: SearchResult } -union SearchResultTarget = Origin | Revision | Release | Content | Directory | Snapshot +""" +A SearchResult object +""" +type SearchResult { + """ + Result target object + """ + target: SearchResultTarget +} + +""" +Possible search result target types +""" enum SearchResultTargetType { origin revision @@ -986,18 +1047,28 @@ } """ -A SearchResult object +Possible search result target nodes """ -type SearchResult { +union SearchResultTargetNode = Origin | Revision | Release | Content | Directory | Snapshot + +""" +A search target object +""" +type SearchResultTarget { """ - Result target type + Search result target type """ - targetType: SearchResultTargetType + type: SearchResultTargetType """ - Result target object + Search result target identifier """ - target: SearchResultTarget + identifier: ID! + + """ + Search result target SWH object + """ + node: SearchResultTargetNode } """ diff --git a/swh/graphql/tests/functional/test_branch_connection.py b/swh/graphql/tests/functional/test_branch_connection.py --- a/swh/graphql/tests/functional/test_branch_connection.py +++ b/swh/graphql/tests/functional/test_branch_connection.py @@ -19,31 +19,33 @@ endCursor } nodes { - targetType name { text } target { - __typename - ...on Branch { - name { - text + type + node { + __typename + ...on Branch { + name { + text + } + } + ...on Revision { + swhid + } + ...on Release { + swhid + } + ...on Content { + swhid + } + ...on Directory { + swhid + } + ...on Snapshot { + swhid } - } - ...on Revision { - swhid - } - ...on Release { - swhid - } - ...on Content { - swhid - } - ...on Directory { - swhid - } - ...on Snapshot { - swhid } } } @@ -63,10 +65,12 @@ assert node == { "name": {"text": "target/revision"}, "target": { - "__typename": "Revision", - "swhid": "swh:1:rev:66c7c1cd9673275037140f2abff7b7b11fc9439c", + "type": "revision", + "node": { + "__typename": "Revision", + "swhid": "swh:1:rev:66c7c1cd9673275037140f2abff7b7b11fc9439c", + }, }, - "targetType": "revision", } @@ -76,8 +80,10 @@ node = data["snapshot"]["branches"]["nodes"][0] assert node == { "name": {"text": "target/alias"}, - "target": {"__typename": "Branch", "name": {"text": "target/revision"}}, - "targetType": "alias", + "target": { + "type": "alias", + "node": {"__typename": "Branch", "name": {"text": "target/revision"}}, + }, } @@ -96,8 +102,8 @@ data, _ = get_branches(client, swhid=swhid, first=10, types=[filter_type]) assert len(data["snapshot"]["branches"]["nodes"]) == count for node in data["snapshot"]["branches"]["nodes"]: - assert node["target"]["__typename"] == target_type - assert node["target"]["swhid"].startswith(swhid_pattern) + assert node["target"]["node"]["__typename"] == target_type + assert node["target"]["node"]["swhid"].startswith(swhid_pattern) @pytest.mark.parametrize( diff --git a/swh/graphql/tests/functional/test_content.py b/swh/graphql/tests/functional/test_content.py --- a/swh/graphql/tests/functional/test_content.py +++ b/swh/graphql/tests/functional/test_content.py @@ -233,11 +233,14 @@ swhid entries(first: 2) { nodes { - targetType target { - ...on Content { - swhid - length + type + identifier + node { + ...on Content { + swhid + length + } } } } @@ -248,6 +251,10 @@ data, _ = utils.get_query_response(client, query_str, swhid=directory_swhid) content_obj = data["directory"]["entries"]["nodes"][1]["target"] assert content_obj == { - "length": 4, - "swhid": "swh:1:cnt:86bc6b377e9d25f9d26777a4a28d08e63e7c5779", + "type": "content", + "identifier": "86bc6b377e9d25f9d26777a4a28d08e63e7c5779", + "node": { + "length": 4, + "swhid": "swh:1:cnt:86bc6b377e9d25f9d26777a4a28d08e63e7c5779", + }, } diff --git a/swh/graphql/tests/functional/test_directory.py b/swh/graphql/tests/functional/test_directory.py --- a/swh/graphql/tests/functional/test_directory.py +++ b/swh/graphql/tests/functional/test_directory.py @@ -65,8 +65,10 @@ release(swhid: $swhid) { swhid target { - ...on Directory { - swhid + node { + ...on Directory { + swhid + } } } } @@ -77,7 +79,7 @@ query_str, swhid="swh:1:rel:ee4d20e80af850cc0f417d25dc5073792c5010d2", ) - assert data["release"]["target"] == { + assert data["release"]["target"]["node"] == { "swhid": "swh:1:dir:0505050505050505050505050505050505050505" } diff --git a/swh/graphql/tests/functional/test_directory_entry.py b/swh/graphql/tests/functional/test_directory_entry.py --- a/swh/graphql/tests/functional/test_directory_entry.py +++ b/swh/graphql/tests/functional/test_directory_entry.py @@ -30,10 +30,13 @@ name { text } - targetType target { - ...on Content { - swhid + type + identifier + node { + ...on Content { + swhid + } } } } @@ -59,16 +62,18 @@ name { text } - targetType target { - ...on Content { - swhid - } - ...on Directory { - swhid - } - ...on Revision { - swhid + type + node { + ...on Content { + swhid + } + ...on Directory { + swhid + } + ...on Revision { + swhid + } } } } @@ -96,8 +101,12 @@ ) assert data["directoryEntry"] == { "name": {"text": entry["name"].decode()}, - "target": {"swhid": str(swhid)} if swhid else None, - "targetType": get_target_type(entry["type"]), + "target": { + "type": get_target_type(entry["type"]), + "node": {"swhid": str(swhid)}, + } + if swhid + else {"type": get_target_type(entry["type"]), "node": None}, } @@ -109,10 +118,12 @@ swhid entries { nodes { - targetType name { text } + target { + type + } } } } @@ -122,7 +133,10 @@ directory_entries = data["directory"]["entries"]["nodes"] assert len(directory_entries) == len(directory.entries) output = [ - {"name": {"text": de.name.decode()}, "targetType": get_target_type(de.type)} + { + "name": {"text": de.name.decode()}, + "target": {"type": get_target_type(de.type)}, + } for de in directory.entries ] for each_entry in output: @@ -140,10 +154,12 @@ swhid entries(nameInclude: $nameInclude) { nodes { - targetType name { text } + target { + type + } } } } @@ -157,7 +173,7 @@ ) for entry in data["directory"]["entries"]["nodes"]: assert name_include in entry["name"]["text"] - assert entry["targetType"] == get_target_type(dir_entry["type"]) + assert entry["target"]["type"] == get_target_type(dir_entry["type"]) def test_directory_entry_connection_filter_by_name_special_chars(client): @@ -167,7 +183,6 @@ directory(swhid: $swhid) { entries(nameInclude: $nameInclude) { nodes { - targetType name { text } @@ -184,5 +199,4 @@ ) assert data["directory"]["entries"]["nodes"][0] == { "name": {"text": "ßßétEÉt"}, - "targetType": "content", } diff --git a/swh/graphql/tests/functional/test_release_node.py b/swh/graphql/tests/functional/test_release_node.py --- a/swh/graphql/tests/functional/test_release_node.py +++ b/swh/graphql/tests/functional/test_release_node.py @@ -50,7 +50,9 @@ base64 } } - targetType + target { + type + } } } """ @@ -81,7 +83,9 @@ } if release.date else None, - "targetType": release.target_type.value, + "target": { + "type": release.target_type.value, + }, } @@ -104,19 +108,21 @@ query_str = """ query getRelease($swhid: SWHID!) { release(swhid: $swhid) { - targetType target { - ...on Revision { - swhid - } - ...on Release { - swhid - } - ...on Directory { - swhid - } - ...on Content { - swhid + type + node { + ...on Revision { + swhid + } + ...on Release { + swhid + } + ...on Directory { + swhid + } + ...on Content { + swhid + } } } } @@ -127,16 +133,19 @@ ) if release_with_target.target_type == ObjectType.REVISION: - target_swhid = get_revisions()[0].swhid() + target = get_revisions()[0] elif release_with_target.target_type == ObjectType.RELEASE: - target_swhid = get_releases()[0].swhid() + target = get_releases()[0] elif release_with_target.target_type == ObjectType.DIRECTORY: - target_swhid = get_directories()[0].swhid() + target = get_directories()[0] elif release_with_target.target_type == ObjectType.CONTENT: - target_swhid = get_contents()[0].swhid() + target = get_contents()[0] assert data["release"] == { - "targetType": release_with_target.target_type.value, - "target": {"swhid": str(target_swhid)}, + "target": { + "type": release_with_target.target_type.value, + # "identifier": target.id.hex(), + "node": {"swhid": str(target.swhid())}, + }, } @@ -150,22 +159,25 @@ query_str = """ query getRelease($swhid: SWHID!) { release(swhid: $swhid) { - targetType target { - ...on Revision { - swhid - message { - text + type + identifier + node { + ...on Revision { + swhid + message { + text + } + } + ...on Release { + swhid + } + ...on Directory { + swhid + } + ...on Content { + swhid } - } - ...on Release { - swhid - } - ...on Directory { - swhid - } - ...on Content { - swhid } } } @@ -174,10 +186,13 @@ data, _ = utils.get_query_response(client, query_str, swhid=str(swhid)) assert data["release"] == { "target": { - "message": {"text": "hello"}, - "swhid": str(get_revisions()[0].swhid()), + "type": "revision", + "identifier": get_revisions()[0].id.hex(), + "node": { + "message": {"text": "hello"}, + "swhid": str(get_revisions()[0].swhid()), + }, }, - "targetType": "revision", } diff --git a/swh/graphql/tests/functional/test_revision.py b/swh/graphql/tests/functional/test_revision.py --- a/swh/graphql/tests/functional/test_revision.py +++ b/swh/graphql/tests/functional/test_revision.py @@ -137,10 +137,12 @@ snapshot(swhid: $swhid) { branches(first: 1, types: [revision]) { nodes { - targetType target { - ...on Revision { - swhid + type + node { + ...on Revision { + swhid + } } } } @@ -149,7 +151,7 @@ } """ data, _ = utils.get_query_response(client, query_str, swhid=snapshot_swhid) - revision_obj = data["snapshot"]["branches"]["nodes"][0]["target"] + revision_obj = data["snapshot"]["branches"]["nodes"][0]["target"]["node"] assert revision_obj == { "swhid": "swh:1:rev:66c7c1cd9673275037140f2abff7b7b11fc9439c" } diff --git a/swh/graphql/tests/functional/test_search.py b/swh/graphql/tests/functional/test_search.py --- a/swh/graphql/tests/functional/test_search.py +++ b/swh/graphql/tests/functional/test_search.py @@ -11,12 +11,14 @@ query doSearch($query: String!, $first: Int!) { search(query: $query, first: $first) { nodes { - targetType target { - ...on Origin { - url - latestVisit { - date + type + node { + ...on Origin { + url + latestVisit { + date + } } } } @@ -35,10 +37,12 @@ "nodes": [ { "target": { - "url": "https://somewhere.org/den/fox", - "latestVisit": {"date": "2018-11-27T17:20:39+00:00"}, + "type": "origin", + "node": { + "url": "https://somewhere.org/den/fox", + "latestVisit": {"date": "2018-11-27T17:20:39+00:00"}, + }, }, - "targetType": "origin", } ], "pageInfo": {"endCursor": "MQ==", "hasNextPage": True}, @@ -51,7 +55,9 @@ query doSearch($query: String!, $first: Int!) { search(query: $query, first: $first) { nodes { - targetType + target { + type + } } pageInfo { hasNextPage diff --git a/swh/graphql/tests/functional/test_snapshot_node.py b/swh/graphql/tests/functional/test_snapshot_node.py --- a/swh/graphql/tests/functional/test_snapshot_node.py +++ b/swh/graphql/tests/functional/test_snapshot_node.py @@ -18,10 +18,12 @@ swhid branches(first:5) { nodes { - targetType name { text } + target { + type + } } } } diff --git a/swh/graphql/tests/functional/test_swhid_resolve.py b/swh/graphql/tests/functional/test_swhid_resolve.py --- a/swh/graphql/tests/functional/test_swhid_resolve.py +++ b/swh/graphql/tests/functional/test_swhid_resolve.py @@ -19,7 +19,9 @@ query_str = """ query resolve($swhid: SWHID!) { resolveSWHID(swhid: $swhid) { - targetType + target { + type + } } } """ @@ -43,7 +45,9 @@ query_str = """ query resolve($swhid: SWHID!) { resolveSWHID(swhid: $swhid) { - targetType + target { + type + } } } """ @@ -57,11 +61,13 @@ query_str = """ query resolve($swhid: SWHID!) { resolveSWHID(swhid: $swhid) { - targetType target { - __typename - ... on Snapshot { - swhid + type + node { + __typename + ... on Snapshot { + swhid + } } } } @@ -72,10 +78,12 @@ "resolveSWHID": [ { "target": { - "__typename": "Snapshot", - "swhid": str(snapshot.swhid()), + "type": "snapshot", + "node": { + "__typename": "Snapshot", + "swhid": str(snapshot.swhid()), + }, }, - "targetType": "snapshot", } ] } @@ -86,11 +94,13 @@ query_str = """ query resolve($swhid: SWHID!) { resolveSWHID(swhid: $swhid) { - targetType target { - __typename - ... on Revision { - swhid + type + node { + __typename + ... on Revision { + swhid + } } } } @@ -101,10 +111,12 @@ "resolveSWHID": [ { "target": { - "__typename": "Revision", - "swhid": str(revision.swhid()), - }, - "targetType": "revision", + "type": "revision", + "node": { + "__typename": "Revision", + "swhid": str(revision.swhid()), + }, + } } ] } @@ -115,11 +127,13 @@ query_str = """ query resolve($swhid: SWHID!) { resolveSWHID(swhid: $swhid) { - targetType - target { - __typename - ... on Release { - swhid + target { + type + node { + __typename + ... on Release { + swhid + } } } } @@ -130,10 +144,12 @@ "resolveSWHID": [ { "target": { - "__typename": "Release", - "swhid": str(release.swhid()), - }, - "targetType": "release", + "type": "release", + "node": { + "__typename": "Release", + "swhid": str(release.swhid()), + }, + } } ] } @@ -144,11 +160,13 @@ query_str = """ query resolve($swhid: SWHID!) { resolveSWHID(swhid: $swhid) { - targetType target { - __typename - ... on Directory { - swhid + type + node { + __typename + ... on Directory { + swhid + } } } } @@ -159,10 +177,12 @@ "resolveSWHID": [ { "target": { - "__typename": "Directory", - "swhid": str(directory.swhid()), - }, - "targetType": "directory", + "type": "directory", + "node": { + "__typename": "Directory", + "swhid": str(directory.swhid()), + }, + } } ] } @@ -173,11 +193,13 @@ query_str = """ query resolve($swhid: SWHID!) { resolveSWHID(swhid: $swhid) { - targetType target { - __typename - ... on Content { - swhid + type + node { + __typename + ... on Content { + swhid + } } } } @@ -188,10 +210,12 @@ "resolveSWHID": [ { "target": { - "__typename": "Content", - "swhid": str(content.swhid()), - }, - "targetType": "content", + "type": "content", + "node": { + "__typename": "Content", + "swhid": str(content.swhid()), + }, + } } ] } diff --git a/swh/graphql/tests/unit/resolvers/test_resolvers.py b/swh/graphql/tests/unit/resolvers/test_resolvers.py --- a/swh/graphql/tests/unit/resolvers/test_resolvers.py +++ b/swh/graphql/tests/unit/resolvers/test_resolvers.py @@ -32,6 +32,7 @@ (rs.release_resolver, resolvers.release.ReleaseNode), (rs.directory_resolver, resolvers.directory.DirectoryNode), (rs.content_by_hashes_resolver, resolvers.content.ContentbyHashesNode), + (rs.generic_target_resolver, resolvers.target.TargetNode), ], ) def test_node_resolver(self, mocker, dummy_node, resolver_func, node_cls): @@ -65,58 +66,6 @@ # assert the right object is returned assert isinstance(connection_obj, connection_cls) - @pytest.mark.parametrize( - "branch_type, node_cls", - [ - ("revision", resolvers.revision.TargetRevisionNode), - ("release", resolvers.release.TargetReleaseNode), - ("directory", resolvers.directory.TargetDirectoryNode), - ("content", resolvers.content.TargetContentNode), - ("snapshot", resolvers.snapshot.TargetSnapshotNode), - ], - ) - def test_snapshot_branch_target_resolver( - self, mocker, dummy_node, branch_type, node_cls - ): - obj = mocker.Mock(targetType=branch_type) - mock_get = mocker.patch.object(node_cls, "_get_node", return_value=dummy_node) - node_obj = rs.snapshot_branch_target_resolver(obj, None) - assert isinstance(node_obj, node_cls) - assert mock_get.assert_called - - @pytest.mark.parametrize( - "target_type, node_cls", - [ - ("revision", resolvers.revision.TargetRevisionNode), - ("release", resolvers.release.TargetReleaseNode), - ("directory", resolvers.directory.TargetDirectoryNode), - ("content", resolvers.content.TargetContentNode), - ], - ) - def test_release_target_resolver(self, mocker, dummy_node, target_type, node_cls): - obj = mocker.Mock(targetType=target_type) - mock_get = mocker.patch.object(node_cls, "_get_node", return_value=dummy_node) - node_obj = rs.release_target_resolver(obj, None) - assert isinstance(node_obj, node_cls) - assert mock_get.assert_called - - @pytest.mark.parametrize( - "target_type, node_cls", - [ - ("directory", resolvers.directory.TargetDirectoryNode), - ("content", resolvers.content.TargetContentNode), - ("revision", resolvers.revision.TargetRevisionNode), - ], - ) - def test_directory_entry_target_resolver( - self, mocker, dummy_node, target_type, node_cls - ): - obj = mocker.Mock(targetType=target_type) - mock_get = mocker.patch.object(node_cls, "_get_node", return_value=dummy_node) - node_obj = rs.directory_entry_target_resolver(obj, None) - assert isinstance(node_obj, node_cls) - assert mock_get.assert_called - def test_union_resolver(self, mocker): obj = mocker.Mock() obj.is_type_of.return_value = "test" diff --git a/swh/graphql/tests/unit/resolvers/test_scalars.py b/swh/graphql/tests/unit/resolvers/test_scalars.py --- a/swh/graphql/tests/unit/resolvers/test_scalars.py +++ b/swh/graphql/tests/unit/resolvers/test_scalars.py @@ -12,7 +12,7 @@ def test_serialize_id(): - assert scalars.serialize_id("test") == "74657374" + assert scalars.serialize_id("test") == "test" assert scalars.serialize_id(b"test") == "74657374"