diff --git a/swh/graphql/resolvers/base_node.py b/swh/graphql/resolvers/base_node.py index 5d5fe72..38fcd12 100644 --- a/swh/graphql/resolvers/base_node.py +++ b/swh/graphql/resolvers/base_node.py @@ -1,70 +1,70 @@ from abc import ABC from collections import namedtuple from swh.graphql.errors import ObjectNotFoundError class BaseNode(ABC): """ Base class for all the Node resolvers """ def __init__(self, obj, info, node_data=None, **kwargs): self.obj = obj self.info = info self.kwargs = kwargs self._node = self._get_node(node_data) # handle the errors, if any, after _node is set self._handle_node_errors() def _get_node(self, node_data): """ Get the node object from the given data if the data (node_data) is none make a function call to get data from backend """ if node_data is None: node_data = self._get_node_data() return self._get_node_from_data(node_data) def _get_node_from_data(self, node_data): """ Get the object from node_data - In case of a dict, covert it to an object + In case of a dict, convert it to an object Override to support different data structures """ if type(node_data) is dict: return namedtuple("NodeObj", node_data.keys())(*node_data.values()) return node_data def _handle_node_errors(self): """ Handle any error related to node data raise an error in case the object returned is None override for specific behaviour """ if self._node is None: raise ObjectNotFoundError("Requested object is not available") def __call__(self, *args, **kw): return self def _get_node_data(self): """ Override for desired behaviour This will be called only when node_data is None """ # FIXME, make this call async (not for v1) return None def __getattr__(self, name): """ Any property defined in the sub-class will get precedence over the _node attributes """ return getattr(self._node, name) def is_type_of(self): return self.__class__.__name__ diff --git a/swh/graphql/resolvers/snapshot.py b/swh/graphql/resolvers/snapshot.py index 86301df..9a8870a 100644 --- a/swh/graphql/resolvers/snapshot.py +++ b/swh/graphql/resolvers/snapshot.py @@ -1,45 +1,45 @@ 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 """ 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 avaialbe in the visit object (self.obj) + snapshot sha1 is available in the visit object (self.obj) """ 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/snapshot_branch.py b/swh/graphql/resolvers/snapshot_branch.py index 6ccf1d6..aef3eff 100644 --- a/swh/graphql/resolvers/snapshot_branch.py +++ b/swh/graphql/resolvers/snapshot_branch.py @@ -1,63 +1,67 @@ from collections import namedtuple from swh.graphql.backends import archive from swh.graphql.utils import utils from swh.storage.interface import PagedResult from .base_connection import BaseConnection from .base_node import BaseNode class SnapshotBranchNode(BaseNode): """ target field for this Node is a UNION in the schema It is resolved in resolvers.resolvers.py """ def _get_node_from_data(self, node_data): """ node_data is not a dict in this case overriding to support this special data structure """ branch_name, branch_obj = node_data node = { "name": branch_name, "type": branch_obj.target_type.value, "target": branch_obj.target, } return namedtuple("NodeObj", node.keys())(*node.values()) + @property + def targetId(self): # To support the schema naming convention + return self._node.target + class SnapshotBranchConnection(BaseConnection): _node_class = SnapshotBranchNode def _get_paged_result(self): """ When branches requested from a snapshot self.obj.id is snapshot_id here (as returned from resolvers/snapshot.py) """ # FIXME, this pagination is not consistent with other connections # FIX in swh-storage to return PagedResult result = archive.Archive().get_snapshot_branches( self.obj.id, after=self._get_after_arg(), first=self._get_first_arg() ) # FIXME Cursor must be a hex to be consistent with # the base class, hack to make that work end_cusrsor = ( result["next_branch"].hex() if result["next_branch"] is not None else None ) return PagedResult( results=result["branches"].items(), next_page_token=end_cusrsor ) def _get_after_arg(self): """ Snapshot branch is using a different cursor; logic to handle that """ # FIXME Cursor must be a hex to be consistent with # the base class, hack to make that work after = utils.get_decoded_cursor(self.kwargs.get("after", "")) return bytes.fromhex(after) diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql index d16ad92..0db2620 100644 --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -1,313 +1,314 @@ scalar SWHID scalar Sha1 scalar DateTime scalar DateTimeZone scalar BinaryText interface Node { id: ID! } # interface SWHIDNode { # id: SWHID! # } interface SWHNode { id: Sha1! } 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: Sha1! 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 { status: String! date: DateTime! snapshot: Snapshot type: String } # FIXME, add OriginSnapshotConnection type Snapshot implements SWHNode { id: Sha1! 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 } type Person { email: BinaryText name: BinaryText fullname: BinaryText } # FIXME, this can be Content, Directory, Snapshot, or Alias as well union BranchTarget = Revision | Release type Branch { name: BinaryText type: String # FIXME, change to an enum + targetId: Sha1 target: BranchTarget } type RevisionConnection { edges: [RevisionEdge] nodes: [Revision] pageInfo: PageInfo! totalCount: Int } type RevisionEdge { cursor: String! node: Revision } type Revision implements SWHNode { id: Sha1! message: BinaryText author: Person committer: Person date: DateTimeZone type: String directoryId: Sha1 directory: Directory parentIds: [Sha1] parents( first: Int after: String ): RevisionConnection # log } # type ReleaseConnection { # } # type ReleasEdge { # } type Release implements SWHNode { id: Sha1! name: BinaryText message: BinaryText author: Person date: DateTimeZone } type DirectoryEntryConnection { edges: [DirectoryEntryEdge] nodes: [DirectoryEntry] pageInfo: PageInfo! totalCount: Int } type DirectoryEntryEdge { cursor: String! node: DirectoryEntry } union DirectoryEntryTarget = Directory | Content type DirectoryEntry { name: BinaryText type: String # FIXME, replace with enum targetId: Sha1 target: DirectoryEntryTarget } type Directory implements SWHNode { id: Sha1! entries( first: Int after: String ): DirectoryEntryConnection } type ContentChecksum { test: String } type ContentType { test: String } type ContentLanguage { test: String } type ContentLicense { test: String } type Content implements SWHNode { id: Sha1! checksum: ContentChecksum # data: filetype: ContentType language: ContentLanguage length: Int license: ContentLicense 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( Sha1: 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( Sha1: String! ): Revision """ Get the release with the given swhid """ release( Sha1: String! ): Release """ Get the directory with the given swhid """ directory( Sha1: String! ): Directory """ Get the content with the given swhid """ content( SWHID: String! ): Content # """ # Search with the given swhid # """ # searchWithSwhid }