diff --git a/swh/graphql/backends/archive.py b/swh/graphql/backends/archive.py index 396476d..00bc55e 100644 --- a/swh/graphql/backends/archive.py +++ b/swh/graphql/backends/archive.py @@ -1,30 +1,35 @@ 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): # FIXME change page_token to base64 encode 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 + ) diff --git a/swh/graphql/resolvers/branch.py b/swh/graphql/resolvers/branch.py new file mode 100644 index 0000000..75c6513 --- /dev/null +++ b/swh/graphql/resolvers/branch.py @@ -0,0 +1,17 @@ +# from swh.graphql.backends import archive + +from .base_connection import BaseConnection + + +class SnapshotBranchConnection(BaseConnection): + def _get_page_result(self): + """ + Branches are avaialble in the snapshot object itself + Not making a query + """ + + return self.obj["branches"] + + # return archive.Archive().get_snapshot_branches( + # after=self._get_after_arg(), first=self._get_first_arg() + # ) diff --git a/swh/graphql/resolvers/resolvers.py b/swh/graphql/resolvers/resolvers.py index aa9ce82..7d336b3 100644 --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -1,127 +1,135 @@ # FIXME, get rid of this module by directly decorating node/connection classes from ariadne import ObjectType from swh.graphql.utils import utils from .origin import OriginConnection, OriginNode from .visit import OriginVisitNode, OriginVisitConnection, VisitStatusConnection from .snapshot import VisitSnapshotNode, SnapshotNode +from .branch import SnapshotBranchConnection query = ObjectType("Query") origin = ObjectType("Origin") origins = ObjectType("OriginConnection") visit = ObjectType("Visit") visitstatus = ObjectType("VisitStatus") snapshot = ObjectType("Snapshot") # 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, } # 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] # Nodes @query.field("origin") def origin_resolver(obj, info, **kw): """ """ # FIXME change to static factory in base class 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)() # Resolvers for node fields # Using ariadne resolution instead of adding properties in Node # That is to avoid looping in NodeConnection @visit.field("id") def visit_id(visit, info): # FIXME, find a better id for visit return utils.encode(f"{visit.origin}-{str(visit.visit)}") @visitstatus.field("id") def visit_status_id(_, info): # FIXME, find a proper id return utils.encode("temp-id") @visitstatus.field("snapshot") def visit_snapshot(obj, info, **kw): resolver = get_node_resolver("visit-snapshot") return resolver(obj, info, **kw)() +@snapshot.field("branches") +def snapshot_branches(obj, info, **kw): + resolver = get_connection_resolver("snapshot-branches") + return resolver(obj, info, **kw)() + + # Connections @query.field("origins") def origins_resolver(obj, info, **kw): # FIXME change to static factory in base class 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)() # Other diff --git a/swh/graphql/resolvers/snapshot.py b/swh/graphql/resolvers/snapshot.py index 779d9ca..1a385db 100644 --- a/swh/graphql/resolvers/snapshot.py +++ b/swh/graphql/resolvers/snapshot.py @@ -1,38 +1,41 @@ from swh.graphql.backends import archive +from swh.graphql.utils import utils from .base_node import BaseNode from .base_connection import BaseConnection class SnapshotNode(BaseNode): """ For directly accessing a snapshot with swhid """ def _get_node(self): """ self.obj is visitstatus here snapshot swhid is avaialbe in the object """ - snapshot_swhid = self.kwargs.get("snapshotId") + snapshot_swhid = utils.str_to_swid(self.kwargs.get("SWHId")) return archive.Archive().get_snapshot(snapshot_swhid) class VisitSnapshotNode(BaseNode): # FIXME, maybe it is a good idea to make a - # common function for both Node classes + # common function for both Node classes (for handling exceptions) """ For accessing a snapshot through the visit type """ def _get_node(self): """ self.obj is visitstatus here snapshot swhid is avaialbe in the object """ snapshot_swhid = self.obj.snapshot return archive.Archive().get_snapshot(snapshot_swhid) class SnapshotConnection(BaseConnection): - pass + """ + To get all the snapshots under an origin + """ diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql index 8be2949..a3d5bc2 100644 --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -1,126 +1,161 @@ scalar SWHId scalar DateTime 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 { - # FIXME, find an idea for a UNIQUE id id: SWHId! url: String! visits( first: Int after: String ): VisitConnection! } -type OriginEdge { - cursor: String! - node: Origin -} - -type OriginConnection { - edges: [OriginEdge] - nodes: [Origin] +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: VisitStatusConnection + status( + first: Int + after: String + ): VisitStatusConnection # origin: Origin # FIXME, this can be added later } -type VisitEdge { - cursor: String! - node: Visit -} - -type VisitConnection { - edges: [VisitEdge] - nodes: [Visit] +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 } -type VisitStatusEdge { - cursor: String! - node: [VisitStatus] +# FIXME, add OriginSnapshotConnection + +type Snapshot implements SWHNode { + id: SWHId! + branches( + first: Int + after: String + ): BranchConnection } -type VisitStatusConnection { - edges: [VisitStatusEdge] - nodes: [VisitStatus] +type BranchConnection { + edges: [BranchConnectionEdge] + nodes: [Branch] pageInfo: PageInfo! totalCount: Int } -type Snapshot implements SWHNode { - id: SWHId! - # branches +type BranchConnectionEdge { + cursor: String! + node: [Branch] +} + +type Branch implements Node { + # FIXME, maybe implement Node is not needed here + # This has no independent existence + id: ID! + name: String + type: String # FIXME, change to an enum } 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 be used to search for an origin + Can also be used to search for an origin """ # FIMXE, use Input types to make this cleaner origins( first: Int after: String - ): OriginConnection! + ): 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( - id: String! + SWHId: String! ): Snapshot + + # """ + # Get all the snapshot for the given origin + # """ + # originSnapshot( + # originUrl: String! + # first: Int + # after: String + # ): SnapshotConnection } -# Remove statusConnection -# Make visit obj by making multiple calls -# Make snapshot object # Make revisionconnection # Make contnetConnection diff --git a/swh/graphql/utils/utils.py b/swh/graphql/utils/utils.py index 50c7337..5ba5028 100644 --- a/swh/graphql/utils/utils.py +++ b/swh/graphql/utils/utils.py @@ -1,17 +1,21 @@ import base64 def encode(text): return base64.b64encode(bytes(text, "utf-8")).decode("utf-8") def get_encoded_cursor(cursor): if cursor is None: return None return base64.b64encode(bytes(cursor, "utf-8")).decode("utf-8") def get_decoded_cursor(cursor): if cursor is None: return None return base64.b64decode(cursor).decode("utf-8") + + +def str_to_swid(str_swid): + return bytearray.fromhex(str_swid)