diff --git a/swh/graphql/app.py b/swh/graphql/app.py index 0e89469..32e91f6 100644 --- a/swh/graphql/app.py +++ b/swh/graphql/app.py @@ -1,20 +1,21 @@ 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, + scalars.binary_text_scalar, ) app = GraphQL(schema, debug=True) diff --git a/swh/graphql/resolvers/branch.py b/swh/graphql/resolvers/branch.py index 75c6513..32a5867 100644 --- a/swh/graphql/resolvers/branch.py +++ b/swh/graphql/resolvers/branch.py @@ -1,17 +1,20 @@ # from swh.graphql.backends import archive +from swh.storage.interface import PagedResult 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() - # ) + # FIXME Mocking PagedResult to make base_connection work + # Fix this in swh-storage + results = [ + {"name": key, "type": value["target_type"]} + for (key, value) in self.obj["branches"].items() + ] + # FIXME, this pagination is broken, fix it with swh-storage + return PagedResult(results=results, next_page_token=self.obj["next_branch"]) diff --git a/swh/graphql/resolvers/resolvers.py b/swh/graphql/resolvers/resolvers.py index 7d336b3..95694f4 100644 --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -1,135 +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 +from .origin import OriginConnection, OriginNode +from .snapshot import SnapshotNode, VisitSnapshotNode +from .visit import OriginVisitConnection, OriginVisitNode, VisitStatusConnection 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/scalars.py b/swh/graphql/resolvers/scalars.py index 876905d..cef43f9 100644 --- a/swh/graphql/resolvers/scalars.py +++ b/swh/graphql/resolvers/scalars.py @@ -1,14 +1,22 @@ from ariadne import ScalarType datetime_scalar = ScalarType("DateTime") swhid_scalar = ScalarType("SWHId") +binary_text_scalar = ScalarType("BinaryText") @datetime_scalar.serializer def serialize_datetime(value): + # FIXME, consider timezone return value.timestamp() @swhid_scalar.serializer def serialize_swid(value): return value.hex() + + +@binary_text_scalar.serializer +def serialize_binary_text(value): + # FIXME, consider non utf-8 + return value.decode("utf-8") diff --git a/swh/graphql/resolvers/snapshot.py b/swh/graphql/resolvers/snapshot.py index 1a385db..582f97f 100644 --- a/swh/graphql/resolvers/snapshot.py +++ b/swh/graphql/resolvers/snapshot.py @@ -1,41 +1,41 @@ from swh.graphql.backends import archive from swh.graphql.utils import utils -from .base_node import BaseNode from .base_connection import BaseConnection +from .base_node import BaseNode 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 = 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 (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): """ To get all the snapshots under an origin """ diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql index a3d5bc2..fab0110 100644 --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -1,161 +1,164 @@ scalar SWHId scalar DateTime +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! 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 } type BranchConnection { edges: [BranchConnectionEdge] nodes: [Branch] pageInfo: PageInfo! totalCount: Int } 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 + name: BinaryText type: String # FIXME, change to an enum + # target: } 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 } # Make revisionconnection # Make contnetConnection