diff --git a/swh/graphql/resolvers/content.py b/swh/graphql/resolvers/content.py index d7034ca..b04f45a 100644 --- a/swh/graphql/resolvers/content.py +++ b/swh/graphql/resolvers/content.py @@ -1,37 +1,38 @@ from swh.graphql.backends import archive from swh.graphql.utils import utils from .base_node import BaseNode class BaseContentNode(BaseNode): def _get_content_by_id(self, content_id): return archive.Archive().get_content(content_id) @property def id(self): return b"test" def is_type_of(self): return "Content" class ContentNode(BaseContentNode): def _get_node_data(self): """ When a content is requested directly - (not from a connection) with an id + with an id """ content_id = utils.str_to_swid(self.kwargs.get("SWHID")) return self._get_content_by_id(content_id)[0] class TargetContentNode(BaseContentNode): def _get_node_data(self): """ When a content is requested from a - directory entry + directory entry or from a release target + content id is obj.target here """ content_id = self.obj.target return self._get_content_by_id(content_id) diff --git a/swh/graphql/resolvers/directory.py b/swh/graphql/resolvers/directory.py index 3907524..124b04b 100644 --- a/swh/graphql/resolvers/directory.py +++ b/swh/graphql/resolvers/directory.py @@ -1,50 +1,49 @@ from swh.graphql.utils import utils from .base_node import BaseNode class BaseDirectoryNode(BaseNode): def _get_directory_by_id(self, directory_id): # Now not fetching any data (schema is exposing just id) # same pattern is used in snapshot resolver # FIXME, use the right API to fetch metadata like name, path return { "id": directory_id, } def is_type_of(self): return "Directory" class DirectoryNode(BaseDirectoryNode): def _get_node_data(self): """ - When a directory is requested directly - (not from a connection) with an id + When a directory is requested directly with an id """ directory_id = utils.str_to_swid(self.kwargs.get("Sha1")) # path = "" return self._get_directory_by_id(directory_id) class RevisionDirectoryNode(BaseDirectoryNode): - # FIXME. maybe this shouls also be resolved - # at the targetdirectory class def _get_node_data(self): """ When a directory is requested from a revision self.obj is revision here self.obj.directoryId is the required dir id (set from resolvers.revision.py:BaseRevisionNode) """ directory_id = self.obj.directoryId return self._get_directory_by_id(directory_id) class TargetDirectoryNode(BaseDirectoryNode): def _get_node_data(self): """ When a directory is requested as a target - obj.target is the sub directory id here + self.obj can be a Release or a DirectoryEntry + + obj.target is the requested directory id here """ return self._get_directory_by_id(self.obj.target) diff --git a/swh/graphql/resolvers/directory_entry.py b/swh/graphql/resolvers/directory_entry.py index e65d987..bfbc9be 100644 --- a/swh/graphql/resolvers/directory_entry.py +++ b/swh/graphql/resolvers/directory_entry.py @@ -1,32 +1,32 @@ from swh.graphql.backends import archive from swh.graphql.utils import utils from .base_connection import BaseConnection from .base_node import BaseNode class DirectoryEntryNode(BaseNode): """ """ @property def targetId(self): # To support the schema naming convention return self._node.target class DirectoryEntryConnection(BaseConnection): _node_class = DirectoryEntryNode def _get_paged_result(self): """ When entries requested from a directory - self.obj.id is directory_id here + self.obj.id is the directory_id here (as returned from resolvers/directory.py) This is not paginated from swh-storgae using dummy pagination """ # FIXME, using dummy(local) pagination, move pagination to backend # To remove localpagination, just drop the paginated call entries = archive.Archive().get_directory_entries(self.obj.id) return utils.paginated(entries, self._get_first_arg(), self._get_after_arg()) diff --git a/swh/graphql/resolvers/origin.py b/swh/graphql/resolvers/origin.py index 58950f2..a3a8ae7 100644 --- a/swh/graphql/resolvers/origin.py +++ b/swh/graphql/resolvers/origin.py @@ -1,24 +1,18 @@ from swh.graphql.backends import archive from .base_connection import BaseConnection from .base_node import BaseNode class OriginNode(BaseNode): def _get_node_data(self): - # FIXME, make this call async (not for v1) return archive.Archive().get_origin(self.kwargs.get("url")) - # @property - # def url(self): - # return "test" - class OriginConnection(BaseConnection): _node_class = OriginNode def _get_paged_result(self): - # FIXME, make this call async (not for v1) return archive.Archive().get_origins( after=self._get_after_arg(), first=self._get_first_arg() ) diff --git a/swh/graphql/resolvers/release.py b/swh/graphql/resolvers/release.py index 6857b36..ba923f2 100644 --- a/swh/graphql/resolvers/release.py +++ b/swh/graphql/resolvers/release.py @@ -1,53 +1,47 @@ from swh.graphql.backends import archive from swh.graphql.utils import utils from .base_node import BaseNode class BaseReleaseNode(BaseNode): def _get_release_by_id(self, release_id): return (archive.Archive().get_releases([release_id]) or None)[0] - @property - def author(self): - # return a PersoneNode object - return self._node.author - @property def targetId(self): # To support the schema naming convention return self._node.target @property def type(self): return self._node.target_type.value def is_type_of(self): """ - is_type_of is required only when requesting - from a connection - + is_type_of is required only when resolving + a UNION type This is for ariadne to return the right type """ return "Release" class ReleaseNode(BaseReleaseNode): """ When the release is requested directly with an id """ def _get_node_data(self): release_id = utils.str_to_swid(self.kwargs.get("Sha1")) return self._get_release_by_id(release_id) class TargetReleaseNode(BaseReleaseNode): """ When a release is requested as a target self.obj could be a snapshotbranch or a release - self.obj.target is the release id here + self.obj.target is the requested release id here """ def _get_node_data(self): return self._get_release_by_id(self.obj.target) diff --git a/swh/graphql/resolvers/resolver_factory.py b/swh/graphql/resolvers/resolver_factory.py index 3c55fda..02af904 100644 --- a/swh/graphql/resolvers/resolver_factory.py +++ b/swh/graphql/resolvers/resolver_factory.py @@ -1,63 +1,62 @@ from .content import ContentNode, TargetContentNode from .directory import DirectoryNode, RevisionDirectoryNode, TargetDirectoryNode from .directory_entry import DirectoryEntryConnection from .origin import OriginConnection, OriginNode from .release import ReleaseNode, TargetReleaseNode from .revision import ParentRevisionConnection, RevisionNode, TargetRevisionNode from .snapshot import SnapshotNode, VisitSnapshotNode from .snapshot_branch import SnapshotBranchConnection from .visit import OriginVisitConnection, OriginVisitNode from .visit_status import VisitStatusConnection # def get_mapping_key(info): # """ # Logic to resolve mapping type # """ # # FIXME, move to utils # if info.path.prev: # return f"{info.path.prev.key}_{info.path.key}" # return info.path.key def get_node_resolver(resolver_type): # FIXME, replace with a proper factory method mapping = { "origin": OriginNode, "visit": OriginVisitNode, "visit-snapshot": VisitSnapshotNode, "snapshot": SnapshotNode, "branch-revision": TargetRevisionNode, "release-revision": TargetRevisionNode, "branch-release": TargetReleaseNode, "release-release": TargetReleaseNode, "release-directory": TargetDirectoryNode, "release-content": TargetContentNode, "revision": RevisionNode, "revision-directory": RevisionDirectoryNode, "release": ReleaseNode, "directory": DirectoryNode, "content": ContentNode, "dir-entry-dir": TargetDirectoryNode, "dir-entry-file": TargetContentNode, } # resolver_type = get_mapping_key(info) # FIXME, get full name if resolver_type not in mapping: raise AttributeError(f"Invalid type request {resolver_type}") return mapping[resolver_type] def get_connection_resolver(resolver_type): # FIXME, replace with a proper factory method mapping = { "origins": OriginConnection, "origin-visits": OriginVisitConnection, "visit-status": VisitStatusConnection, "snapshot-branches": SnapshotBranchConnection, "revision-parents": ParentRevisionConnection, "directory-entries": DirectoryEntryConnection, - # revision-parents } # resolver_type = get_mapping_key(info) # FIXME, get full name if resolver_type not in mapping: raise AttributeError(f"Invalid type request {resolver_type}") return mapping[resolver_type] diff --git a/swh/graphql/resolvers/resolvers.py b/swh/graphql/resolvers/resolvers.py index 5e32e6c..e3ea1b6 100644 --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -1,172 +1,172 @@ """ High level resolvers Any schema attribute can be resolved by any of the following ways and in the following priority order - In this module using an annotation (eg: @visitstatus.field("snapshot")) - As a property in the Node object (eg: resolvers.visit.OriginVisitNode.id) - As an attribute/item in the object/dict returned by the backend (eg: Origin.url) """ from ariadne import ObjectType, UnionType from .resolver_factory import get_connection_resolver, get_node_resolver query = ObjectType("Query") origin = ObjectType("Origin") visit = ObjectType("Visit") visit_status = ObjectType("VisitStatus") snapshot = ObjectType("Snapshot") snapshot_branch = ObjectType("Branch") revision = ObjectType("Revision") release = ObjectType("Release") directory = ObjectType("Directory") directory_entry = ObjectType("DirectoryEntry") branch_target = UnionType("BranchTarget") release_target = UnionType("ReleaseTarget") directory_entry_target = UnionType("DirectoryEntryTarget") # Node resolvers -# A node resolver can return a node object or a data structure +# A node resolver should return an instance of BaseNode @query.field("origin") def origin_resolver(obj, info, **kw): """ """ resolver = get_node_resolver("origin") return resolver(obj, info, **kw)() @query.field("visit") def visit_resolver(obj, info, **kw): """ """ resolver = get_node_resolver("visit") return resolver(obj, info, **kw)() @query.field("snapshot") def snapshot_resolver(obj, info, **kw): """ """ resolver = get_node_resolver("snapshot") return resolver(obj, info, **kw)() @visit_status.field("snapshot") def visit_snapshot_resolver(obj, info, **kw): resolver = get_node_resolver("visit-snapshot") return resolver(obj, info, **kw)() @snapshot_branch.field("target") def snapshot_branch_target_resolver(obj, info, **kw): """ Snapshot branch target can be a revision or a release """ resolver_type = f"branch-{obj.type}" resolver = get_node_resolver(resolver_type) return resolver(obj, info, **kw)() @query.field("revision") def revision_resolver(obj, info, **kw): resolver = get_node_resolver("revision") return resolver(obj, info, **kw)() @revision.field("directory") def revision_directory_resolver(obj, info, **kw): resolver = get_node_resolver("revision-directory") return resolver(obj, info, **kw)() @query.field("release") def release_resolver(obj, info, **kw): resolver = get_node_resolver("release") return resolver(obj, info, **kw)() @release.field("target") def release_target_resolver(obj, info, **kw): """ release target can be a release, revision, directory or content obj is release here, target type is obj.target_type """ resolver_type = f"release-{obj.target_type.value}" resolver = get_node_resolver(resolver_type) return resolver(obj, info, **kw)() @query.field("directory") def directory_resolver(obj, info, **kw): resolver = get_node_resolver("directory") return resolver(obj, info, **kw)() @directory_entry.field("target") def directory_entry_target_resolver(obj, info, **kw): """ directory entry target can be a directory or a content """ resolver_type = f"dir-entry-{obj.type}" resolver = get_node_resolver(resolver_type) return resolver(obj, info, **kw)() @query.field("content") def content_resolver(obj, info, **kw): resolver = get_node_resolver("content") return resolver(obj, info, **kw)() # Connection resolvers -# A connection resolver will return a sub class of BaseConnection +# A connection resolver should return an instance of BaseConnection @query.field("origins") def origins_resolver(obj, info, **kw): resolver = get_connection_resolver("origins") return resolver(obj, info, **kw)() @origin.field("visits") def visits_resolver(obj, info, **kw): resolver = get_connection_resolver("origin-visits") return resolver(obj, info, **kw)() @visit.field("status") def visitstatus_resolver(obj, info, **kw): resolver = get_connection_resolver("visit-status") return resolver(obj, info, **kw)() @snapshot.field("branches") def snapshot_branches_resolver(obj, info, **kw): resolver = get_connection_resolver("snapshot-branches") return resolver(obj, info, **kw)() @revision.field("parents") def revision_parent_resolver(obj, info, **kw): resolver = get_connection_resolver("revision-parents") return resolver(obj, info, **kw)() @directory.field("entries") def directory_entry_resolver(obj, info, **kw): resolver = get_connection_resolver("directory-entries") return resolver(obj, info, **kw)() # Any other type of resolver @release_target.type_resolver @directory_entry_target.type_resolver @branch_target.type_resolver def union_resolver(obj, *_): """ Generic resolver for all the union types """ return obj.is_type_of() diff --git a/swh/graphql/resolvers/revision.py b/swh/graphql/resolvers/revision.py index 4c5e90b..435d1ac 100644 --- a/swh/graphql/resolvers/revision.py +++ b/swh/graphql/resolvers/revision.py @@ -1,87 +1,74 @@ from swh.graphql.backends import archive from swh.graphql.utils import utils from .base_connection import BaseConnection from .base_node import BaseNode class BaseRevisionNode(BaseNode): def _get_revision_by_id(self, revision_id): - # FIXME, make this call async return (archive.Archive().get_revisions([revision_id]) or None)[0] - @property - def author(self): - # return a PersoneNode object - return self._node.author - - @property - def committer(self): - # return a PersoneNode object - return self._node.committer - @property def parentIds(self): # To support the schema naming convention return self._node.parents @property def directoryId(self): # To support the schema naming convention """ """ return self._node.directory @property def type(self): return self._node.type.value def is_type_of(self): """ - is_type_of is required only when - requesting from a connection - - This is for ariadne to return the correct type in schema + is_type_of is required only when resolving + a UNION type + This is for ariadne to return the right type """ return "Revision" class RevisionNode(BaseRevisionNode): """ - When the revision is requested directly - (not from a connection) with an id + When the revision is requested directly with an id """ def _get_node_data(self): revision_id = utils.str_to_swid(self.kwargs.get("Sha1")) return self._get_revision_by_id(revision_id) class TargetRevisionNode(BaseRevisionNode): """ When a revision is requested as a target self.obj could be a snapshotbranch or a release - self.obj.target is the revision id here + self.obj.target is the requested revision id here """ def _get_node_data(self): """ self.obj.target is the Revision id """ return self._get_revision_by_id(self.obj.target) class ParentRevisionConnection(BaseConnection): """ When parent revisions requested from a revision - self.obj is the child revision here + self.obj is the child revision self.obj.parentIds is the list of - parent revisions + requested revisions """ _node_class = BaseRevisionNode def _get_paged_result(self): # FIXME, using dummy(local) pagination, move pagination to backend # To remove localpagination, just drop the paginated call parents = archive.Archive().get_revisions(self.obj.parentIds) return utils.paginated(parents, self._get_first_arg(), self._get_after_arg()) diff --git a/swh/graphql/resolvers/snapshot.py b/swh/graphql/resolvers/snapshot.py index 9a8870a..04e0099 100644 --- a/swh/graphql/resolvers/snapshot.py +++ b/swh/graphql/resolvers/snapshot.py @@ -1,45 +1,36 @@ from swh.graphql.utils import utils from .base_node import BaseNode class BaseSnapshotNode(BaseNode): def _get_snapshot_by_id(self, snapshot_id): # Now not fetching any data (schema is exposing just id) # same pattern is used in directory resolver return { "id": snapshot_id, } class SnapshotNode(BaseSnapshotNode): """ - For directly accessing a snapshot with swhid + For directly accessing a snapshot with an Id """ def _get_node_data(self): """ """ - # FIXME, use methods from SWH core snapshot_id = utils.str_to_swid(self.kwargs.get("Sha1")) return self._get_snapshot_by_id(snapshot_id) class VisitSnapshotNode(BaseSnapshotNode): """ For accessing a snapshot from a visitstatus type """ def _get_node_data(self): """ self.obj is visitstatus here - snapshot sha1 is available in the visit object (self.obj) + self.obj.snapshot is the requested snapshot id """ return self._get_snapshot_by_id(self.obj.snapshot) - - -# class SnapshotConnection(BaseConnection): -# """ -# To get all the snapshots under an origin -# """ - -# _node_class = SnapshotNode diff --git a/swh/graphql/resolvers/visit.py b/swh/graphql/resolvers/visit.py index 96c766f..b7739c0 100644 --- a/swh/graphql/resolvers/visit.py +++ b/swh/graphql/resolvers/visit.py @@ -1,32 +1,30 @@ from swh.graphql.backends import archive from swh.graphql.utils import utils from .base_connection import BaseConnection from .base_node import BaseNode class OriginVisitNode(BaseNode): def _get_node_data(self): - # FIXME, make this call async (not for v1) return archive.Archive().get_origin_visit( self.kwargs.get("originUrl"), int(self.kwargs.get("id")) ) @property def id(self): # FIXME, use a better id return utils.encode(f"{self.origin}-{str(self.visit)}") class OriginVisitConnection(BaseConnection): _node_class = OriginVisitNode def _get_paged_result(self): """ Get the visits for the given origin parent obj (self.obj) is origin here """ - # FIXME, make this call async (not for v1) return archive.Archive().get_origin_visits( self.obj.url, after=self._get_after_arg(), first=self._get_first_arg() ) diff --git a/swh/graphql/resolvers/visit_status.py b/swh/graphql/resolvers/visit_status.py index 470be8b..48d85f4 100644 --- a/swh/graphql/resolvers/visit_status.py +++ b/swh/graphql/resolvers/visit_status.py @@ -1,31 +1,24 @@ from swh.graphql.backends import archive -from swh.graphql.utils import utils from .base_connection import BaseConnection from .base_node import BaseNode class VisitStatusNode(BaseNode): - def _get_node_data(self): - """ """ - - @property - def id(self): - # FIXME, find logic to generate an id - return utils.encode("dummy-id") + """ """ class VisitStatusConnection(BaseConnection): """ self.obj is the visit object """ _node_class = VisitStatusNode def _get_paged_result(self): return archive.Archive().get_visit_status( self.obj.origin, self.obj.visit, after=self._get_after_arg(), first=self._get_first_arg(), )