diff --git a/swh/graphql/app.py b/swh/graphql/app.py index 0be52b9..0e89469 100644 --- a/swh/graphql/app.py +++ b/swh/graphql/app.py @@ -1,19 +1,20 @@ from ariadne import gql, load_schema_from_path, make_executable_schema from ariadne.asgi import GraphQL from .resolvers import resolvers, scalars type_defs = gql(load_schema_from_path("swh/graphql/schema/schema.graphql")) schema = make_executable_schema( type_defs, resolvers.query, resolvers.origin, resolvers.origins, resolvers.visit, resolvers.visitstatus, + resolvers.snapshot, scalars.datetime_scalar, scalars.swhid_scalar, ) app = GraphQL(schema, debug=True) diff --git a/swh/graphql/backends/archive.py b/swh/graphql/backends/archive.py index 70da8ba..396476d 100644 --- a/swh/graphql/backends/archive.py +++ b/swh/graphql/backends/archive.py @@ -1,27 +1,30 @@ 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) diff --git a/swh/graphql/resolvers/resolvers.py b/swh/graphql/resolvers/resolvers.py index e07b3ba..aa9ce82 100644 --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -1,96 +1,127 @@ +# 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 OriginVisit, OriginVisitConnection, VisitStatusConnection +from .visit import OriginVisitNode, OriginVisitConnection, VisitStatusConnection +from .snapshot import VisitSnapshotNode, SnapshotNode 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_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(info): +def get_node_resolver(resolver_type): # FIXME, replace with a proper factory method - mapping = {"origin": OriginNode, "visit": OriginVisit} - resolver_type = info.path.key # get_mapping_key(info) # FIXME, get full name + 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(info): +def get_connection_resolver(resolver_type): # FIXME, replace with a proper factory method mapping = { "origins": OriginConnection, - "visits": OriginVisitConnection, - "status": VisitStatusConnection, + "origin-visits": OriginVisitConnection, + "visit-status": VisitStatusConnection, } - resolver_type = info.path.key # get_mapping_key(info) # FIXME, get full name + # 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("visit") @query.field("origin") -def node_resolver(obj, info, **kw): +def origin_resolver(obj, info, **kw): """ - Resolver for all the node types """ # FIXME change to static factory in base class - resolver = get_node_resolver(info) + 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 better id + # FIXME, find a proper id return utils.encode("temp-id") @visitstatus.field("snapshot") -def visit_status_id(status, info, **kw): - # return SnapshotNode() - return status.snapshot.hex() +def visit_snapshot(obj, info, **kw): + resolver = get_node_resolver("visit-snapshot") + return resolver(obj, info, **kw)() # Connections -@visit.field("status") -@origin.field("visits") @query.field("origins") -def connection_resolver(obj, info, **kw): +def origins_resolver(obj, info, **kw): # FIXME change to static factory in base class - resolver = get_connection_resolver(info) + 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 2ae2839..779d9ca 100644 --- a/swh/graphql/resolvers/snapshot.py +++ b/swh/graphql/resolvers/snapshot.py @@ -1 +1,38 @@ -pass +from swh.graphql.backends import archive + +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") + 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 + """ + 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 diff --git a/swh/graphql/resolvers/visit.py b/swh/graphql/resolvers/visit.py index 7755084..02b8292 100644 --- a/swh/graphql/resolvers/visit.py +++ b/swh/graphql/resolvers/visit.py @@ -1,35 +1,34 @@ from swh.graphql.backends import archive from .base_connection import BaseConnection from .base_node import BaseNode -class OriginVisit(BaseNode): +class OriginVisitNode(BaseNode): def _get_node(self): # FIXME, make this call async (not for v1) return archive.Archive().get_origin_visit( self.kwargs.get("originUrl"), int(self.kwargs.get("id")) ) class OriginVisitConnection(BaseConnection): def _get_page_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() ) class VisitStatusConnection(BaseConnection): def _get_page_result(self): - s = archive.Archive().get_visit_status( + return archive.Archive().get_visit_status( self.obj.origin, self.obj.visit, after=self._get_after_arg(), first=self._get_first_arg(), ) - return s diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql index 0123d81..8be2949 100644 --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -1,122 +1,126 @@ scalar SWHId scalar DateTime interface Node { id: ID! } interface SWHNode { id: SWHId! } type PageInfo { endCursor: String hasNextPage: Boolean! } 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] pageInfo: PageInfo! totalCount: Int } type Visit implements Node { id: ID! date: DateTime! type: String status: VisitStatusConnection # origin: Origin # FIXME, this can be added later } type VisitEdge { cursor: String! node: Visit } type VisitConnection { edges: [VisitEdge] nodes: [Visit] pageInfo: PageInfo! totalCount: Int } type VisitStatus implements Node { id: ID! status: String! date: DateTime! - snapshot: String + snapshot: Snapshot type: String } type VisitStatusEdge { cursor: String! node: [VisitStatus] } type VisitStatusConnection { edges: [VisitStatusEdge] nodes: [VisitStatus] pageInfo: PageInfo! totalCount: Int } -# type Snapshot implements SWHNode { -# id: SWHId! -# # branches -# } +type Snapshot implements SWHNode { + id: SWHId! + # branches +} 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 """ # 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 + + snapshot( + id: String! + ): Snapshot } # Remove statusConnection # Make visit obj by making multiple calls # Make snapshot object # Make revisionconnection # Make contnetConnection