diff --git a/swh/graphql/backends/archive.py b/swh/graphql/backends/archive.py index 5051ed8..e227822 100644 --- a/swh/graphql/backends/archive.py +++ b/swh/graphql/backends/archive.py @@ -1,40 +1,48 @@ from swh.storage import get_storage class Archive: def __init__(self): # FIXME, setup config self.storage = get_storage( cls="remote", url="http://moma.internal.softwareheritage.org:5002" ) def get_origin(self, url): return self.storage.origin_get([url])[0] def get_origins(self, after=None, first=50): return self.storage.origin_list(page_token=after, limit=first) def get_origin_visits(self, origin_url, after=None, first=50): return self.storage.origin_visit_get(origin_url, page_token=after, limit=first) def get_origin_visit(self, origin_url, visit_id): return self.storage.origin_visit_get_by(origin_url, visit_id) def get_visit_status(self, origin_url, visit_id, after=None, first=50): return self.storage.origin_visit_status_get( origin_url, visit_id, page_token=after, limit=first ) def get_snapshot(self, snapshot_swhid): return self.storage.snapshot_get(snapshot_swhid) def get_snapshot_branches(self, snapshot, after=None, first=50): return self.storage.snapshot_get_branches( snapshot, branches_from=after, branches_count=first ) def get_revision(self, revision_id): return self.storage.revision_get(revision_ids=[revision_id]) def get_release(self, release_id): return self.storage.release_get(releases=[release_id]) + + def get_directory(self, directory_id): + # FIXME, only for tests + return self.storage.directory_ls(directory_id) + + def get_content(self, content_id): + # FIXME, only for tests + return self.storage.content_find({"sha1_git": content_id}) diff --git a/swh/graphql/resolvers/content.py b/swh/graphql/resolvers/content.py index 412265e..39bfbe1 100644 --- a/swh/graphql/resolvers/content.py +++ b/swh/graphql/resolvers/content.py @@ -1,7 +1,23 @@ +from swh.graphql.backends import archive +from swh.graphql.utils import utils + from .base_node import BaseNode -class ContentNode(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" + + +class ContentNode(BaseContentNode): def _get_node_data(self): """ + When a content is requested directly + (not from a connection) with an id """ + content_id = utils.str_to_swid(self.kwargs.get("SWHId")) + return self._get_content_by_id(content_id)[0] diff --git a/swh/graphql/resolvers/directory.py b/swh/graphql/resolvers/directory.py index e8f2b35..5b9a601 100644 --- a/swh/graphql/resolvers/directory.py +++ b/swh/graphql/resolvers/directory.py @@ -1,7 +1,33 @@ +from swh.graphql.backends import archive +from swh.graphql.utils import utils + from .base_node import BaseNode -class DirectoryNode(BaseNode): +class BaseDirectoryNode(BaseNode): + def _get_directory_by_id(self, directory_id): + return archive.Archive().get_directory(directory_id) + + @property + def entries(self): + # FIXME, return a paginated list of + # directory or contnet node object + return self._node[0]["name"] + + @property + def name(self): + return b"test-name" + + @property + def id(self): + return b"test-id" + + +class DirectoryNode(BaseDirectoryNode): def _get_node_data(self): """ + When a directory is requested directly + (not from a connection) with an id """ + directory_id = utils.str_to_swid(self.kwargs.get("SWHId")) + return self._get_directory_by_id(directory_id) diff --git a/swh/graphql/resolvers/resolver_factory.py b/swh/graphql/resolvers/resolver_factory.py index ca0f37a..3d51901 100644 --- a/swh/graphql/resolvers/resolver_factory.py +++ b/swh/graphql/resolvers/resolver_factory.py @@ -1,48 +1,52 @@ +from .content import ContentNode +from .directory import DirectoryNode from .origin import OriginConnection, OriginNode from .release import BranchReleaseNode, ReleaseNode from .revision import BranchRevisionNode, RevisionNode 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": BranchRevisionNode, "branch-release": BranchReleaseNode, "revision": RevisionNode, "release": ReleaseNode, + "directory": DirectoryNode, + "content": ContentNode, } # 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, } # 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 c16a312..7153333 100644 --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -1,114 +1,126 @@ """ 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") branch_target = UnionType("BranchTarget") # Node resolvers # A node resolver can return a node object or a data structure @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)() @query.field("release") def release_resolver(obj, info, **kw): resolver = get_node_resolver("release") return resolver(obj, info, **kw)() +@query.field("directory") +def directory_resolver(obj, info, **kw): + resolver = get_node_resolver("directory") + 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 @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)() # Any other type of 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/schema/schema.graphql b/swh/graphql/schema/schema.graphql index 17fee99..fc78171 100644 --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -1,246 +1,251 @@ scalar SWHId scalar DateTime scalar DateTimeZone scalar BinaryText interface Node { id: ID! } interface SWHNode { id: SWHId! } type PageInfo { endCursor: String hasNextPage: Boolean! } type OriginConnection { edges: [OriginEdge] nodes: [Origin] pageInfo: PageInfo! totalCount: Int } type OriginEdge { cursor: String! node: Origin } type Origin implements SWHNode { id: SWHId! # FIXME, this is not swhid url: String! visits( first: Int after: String ): VisitConnection! } type VisitConnection { edges: [VisitEdge] nodes: [Visit] pageInfo: PageInfo! totalCount: Int } type VisitEdge { cursor: String! node: Visit } type Visit implements Node { id: ID! date: DateTime! type: String status( first: Int after: String ): VisitStatusConnection # origin: Origin # FIXME, this can be added later } type VisitStatusConnection { edges: [VisitStatusEdge] nodes: [VisitStatus] pageInfo: PageInfo! totalCount: Int } type VisitStatusEdge { cursor: String! node: VisitStatus } type VisitStatus implements Node { id: ID! status: String! date: DateTime! snapshot: Snapshot type: String } # FIXME, add OriginSnapshotConnection type Snapshot implements SWHNode { id: SWHId! branches( first: Int after: String ): BranchConnection # releases( # first: Int # after: String # ): ReleaseConnection # FIXME, add alias type as well } type BranchConnection { edges: [BranchConnectionEdge] nodes: [Branch] pageInfo: PageInfo! totalCount: Int } type BranchConnectionEdge { cursor: String! node: [Branch] } # FIXME, this could be alias or Directory as well union BranchTarget = Revision | Release type Branch implements Node { id: ID! name: BinaryText type: String # FIXME, change to an enum target: BranchTarget } # type RevisionConnection { # } # type RevisionEdge { # } type Person { email: BinaryText name: BinaryText fullname: BinaryText } type Revision implements SWHNode { id: SWHId! message: BinaryText author: Person committer: Person date: DateTimeZone type: String directory: SWHId } # type ReleaseConnection { # } # type ReleasEdge { # } type Release implements SWHNode { id: SWHId! name: BinaryText message: BinaryText author: Person date: DateTimeZone } type Directory implements SWHNode { id: SWHId! + name: BinaryText + entries: BinaryText # FIXME, change to Union type } type Content implements SWHNode { id: SWHId! + status: String } type Query { """ Get an origin with its url """ # FIXME, find some unique id to help cache # maybe base64 encode the URL origin( url: String! ): Origin """ Get a list of origins matching the given filters Can also be used to search for an origin """ # FIMXE, use Input types to make this cleaner origins( first: Int after: String ): OriginConnection """ Get a visit object with its id and/or origin and visit id """ # FIXME, find some unique id to help cache visit( originUrl: String! id: String! ): Visit """ Get a snapshot with SWHId """ snapshot( SWHId: String! ): Snapshot # """ # Get all the snapshot for the given origin # """ # originSnapshot( # originUrl: String! # first: Int # after: String # ): SnapshotConnection """ Get the revision with the given swhid """ revision( SWHId: String! ): Revision """ Get the release with the given swhid """ release( SWHId: String! ): Release - # """ - # Get the directory with the given swhid - # """ - # directory + """ + Get the directory with the given swhid + """ + directory( + SWHId: String! + ): Directory - # """ - # Get the content with the given swhid - # """ - # content( - # SWHId: String! - # ): Content + """ + Get the content with the given swhid + """ + content( + SWHId: String! + ): Content # """ # Search with the given swhid # """ # searchWithSwhid }