def get_neo4j_node(resource_name, uid): """ Get a Neo4j node based on a label and unique identifier. :param str resource_name: a neomodel model label :param str uid: a string of the unique identifier defined in the neomodel model :return: a neomodel model object :raises ValidationError: if the requested resource doesn't exist or doesn't have a UniqueIdProperty """ # To prevent a ciruclar import, we must import this here from estuary.models import all_models for model in all_models: if model.__label__.lower() == resource_name.lower(): for _, prop_def in model.__all_properties__: if isinstance(prop_def, UniqueIdProperty): return model.nodes.get_or_none(**{prop_def.name: uid}) # Some models don't have unique ID's and those should be skipped models_wo_uid = ('DistGitRepo', 'DistGitBranch') model_names = [ model.__name__.lower() for model in all_models if model.__name__ not in models_wo_uid ] error = ( 'The requested resource "{0}" is invalid. Choose from the following: ' '{1}, and {2}.'.format(resource_name, ', '.join(model_names[:-1]), model_names[-1])) raise ValidationError(error)
def find_or_none(cls, identifier): """ Find the node using the supplied identifier. :param str identifier: the identifier to search the node by :return: the node or None :rtype: EstuaryStructuredNode or None """ uid = identifier if re.match(r'^\d+$', uid): # The identifier is an ID return cls.nodes.get_or_none(id_=uid) elif uid.endswith('.src.rpm'): # The identifer is likely an NVR with .src.rpm at the end, so strip that part of it # so it can be treated like a normal NVR uid = uid[:-8] if len(uid.rsplit('-', 2)) == 3: # The identifier looks like an NVR nvr = uid.rsplit('-', 2) return cls.nodes.get_or_none(name=nvr[0], version=nvr[1], release=nvr[2]) raise ValidationError( '"{0}" is not a valid identifier'.format(identifier))
def get_neo4j_node(resource_name, uid): """ Get a Neo4j node based on a label and unique identifier. :param str resource_name: a neomodel model label :param str uid: a string of the unique identifier defined in the neomodel model :return: a neomodel model object :raises ValidationError: if the requested resource doesn't exist or doesn't have a UniqueIdProperty """ # To prevent a ciruclar import, we must import this here from estuary.models import all_models for model in all_models: if model.__label__.lower() == resource_name.lower(): try: return model.find_or_none(uid) except RuntimeError: # There is no UniqueIdProperty on this model so raise an exception models_wo_uid = ('DistGitRepo', 'DistGitBranch') model_names = [ model.__name__.lower() for model in all_models if model.__name__ not in models_wo_uid ] error = ( 'The requested resource "{0}" is invalid. Choose from the following: ' '{1}, and {2}.'.format(resource_name, ', '.join(model_names[:-1]), model_names[-1])) raise ValidationError(error)
def create_story_query(item, uid_name, uid, reverse=False, limit=False): """ Create a raw cypher query for story of an artifact. :param node item: a Neo4j node whose story is requested by the user :param str uid_name: name of node's UniqueIdProperty :param str uid: value of node's UniqueIdProperty :param bool reverse: boolean value to specify the direction to proceed from current node corresponding to the story_flow :return: a string containing raw cypher query to retrieve the story of an artifact from Neo4j :rtype: str """ # To avoid circular imports from estuary.models import story_flow_list query = '' if reverse is True: rel_label = 'backward_relationship' node_label = 'backward_label' else: rel_label = 'forward_relationship' node_label = 'forward_label' curr_node_label = item.__label__ if curr_node_label not in story_flow_list: raise ValidationError( 'The story is not available for this kind of resource') while True: curr_node_info = story_flow(curr_node_label) if not curr_node_info: break if curr_node_label == item.__label__: query = """\ MATCH ({var}:{label} {{{uid_name}:"{uid}"}}) CALL apoc.path.expandConfig({var}, {{sequence:\'{label} """.format(var=curr_node_label.lower(), label=curr_node_label, uid_name=uid_name.rstrip('_'), uid=uid) query += ', {0}, {1}'.format(curr_node_info[rel_label], curr_node_info[node_label]) curr_node_label = curr_node_info[node_label] if query: query += """\ \', minLevel:1}) YIELD path RETURN path ORDER BY length(path) DESC """ if query and limit: query += ' LIMIT 1' return query
def get_story_nodes(self, item, reverse=False, limit=False): """ Create a raw cypher query for story of an artifact and query neo4j with it. :param node item: a Neo4j node whose story is requested by the user :kwarg bool reverse: specifies the direction to proceed from current node corresponding to the story_flow :kwarg bool limit: specifies if LIMIT keyword should be added to the created cypher query :return: story paths for a particular artifact :rtype: list """ query = '' if reverse is True: rel_label = 'backward_relationship' node_label = 'backward_label' else: rel_label = 'forward_relationship' node_label = 'forward_label' curr_node_label = item.__label__ if curr_node_label not in self.story_flow_list: raise ValidationError( 'The story is not available for this kind of resource') while True: curr_node_info = self.story_flow(curr_node_label) if not curr_node_info: break if curr_node_label == item.__label__: query = """\ MATCH ({var}:{label}) WHERE id({var})= {node_id} CALL apoc.path.expandConfig({var}, {{sequence:\'{label} """.format(var=curr_node_label.lower(), label=curr_node_label, node_id=item.id) query += ', {0}, {1}'.format(curr_node_info[rel_label], curr_node_info[node_label]) curr_node_label = curr_node_info[node_label] if query: query += """\ \', minLevel:1}) YIELD path RETURN path ORDER BY length(path) DESC """ if query and limit: query += ' LIMIT 1' results = [] if query: results, _ = db.cypher_query(query) return results
def find_or_none(cls, identifier): """ Find the node using the supplied identifier. :param str identifier: the identifier to search the node by :return: the node or None :rtype: EstuaryStructuredNode or None """ uid = identifier if uid.lower().startswith('rhbz'): uid = uid[4:] if uid.startswith('#'): uid = uid[1:] # If we are left with something other than digits, then it is an invalid identifier if not re.match(r'^\d+$', uid): raise ValidationError( '"{0}" is not a valid identifier'.format(identifier)) return cls.nodes.get_or_none(id_=uid)
def find_or_none(cls, identifier): """ Find the node using the supplied identifier. :param str identifier: the identifier to search the node by :return: the node or None :rtype: EstuaryStructuredNode or None """ if re.match(r'^\d+$', identifier): # The identifier is an ID return cls.nodes.get_or_none(id_=identifier) elif re.match(r'^RH[A-Z]{2}-\d{4}:\d+-\d+$', identifier): # The identifier is a full advisory name return cls.nodes.get_or_none(advisory_name=identifier) elif re.match(r'^RH[A-Z]{2}-\d{4}:\d+$', identifier): # The identifier is most of the advisory name, so return the latest iteration of this # advisory return cls.nodes.filter(advisory_name__regex=r'^{0}-\d+$'.format(identifier))\ .order_by('advisory_name').first_or_none() else: raise ValidationError('"{0}" is not a valid identifier'.format(identifier))
def get_artifact_relationships(resource, uid, relationship): """ Get one-to-many relationships of a particular artifact. :param str resource: a resource name that maps to a neomodel class :param str uid: the value of the UniqueIdProperty to query with :param str relationship: relationship to expand :return: a Flask JSON response :rtype: flask.Response :raises NotFound: if the item is not found :raises ValidationError: if an invalid resource/relationship was requested """ item = get_neo4j_node(resource, uid) if not item: raise NotFound('This item does not exist') if relationship not in [rel[0] for rel in item.__all_relationships__]: raise ValidationError( 'Please provide a valid relationship name for {0} with uid {1}'. format(resource, uid)) rel_display_name = relationship.replace('_', ' ') results = { 'data': [], 'meta': { 'description': '{0} of {1}'.format(rel_display_name, item.display_name) } } related_nodes = getattr(item, relationship).match() for node in related_nodes: serialized_node = node.serialized_all serialized_node['resource_type'] = node.__label__ serialized_node['display_name'] = node.display_name results['data'].append(serialized_node) return jsonify(results)
def get_siblings(resource, uid): """ Get siblings of next/previous node that are correlated to the node in question. :param str resource: a resource name that maps to a neomodel class :param str uid: the value of the UniqueIdProperty to query with :return: a Flask JSON response :rtype: flask.Response :raises NotFound: if the item is not found :raises ValidationError: if an invalid resource was requested """ story_type_mapper = { 'container': 'ContainerStoryManager', 'module': 'ModuleStoryManager' } story_type = request.args.get('story_type', 'container').lower() story_manager_class = story_type_mapper.get(story_type) if story_manager_class: story_manager = getattr(estuary.utils.story, story_manager_class)() else: raise ValidationError( 'Supplied story type is invalid. Select from: {0}'.format( ', '.join(current_app.config['STORY_MANAGER_SEQUENCE']))) # This is the node that is part of a story which has the relationship to the desired # sibling nodes story_node = get_neo4j_node(resource, uid) if not story_node: raise NotFound('This item does not exist') story_node_story_flow = story_manager.story_flow(story_node.__label__) # If backward_rel is true, we fetch siblings of the previous node, next node otherwise. # For example, if you pass in an advisory and want to know the Freshmaker events triggered from # that advisory, backward_rel would be false. If you want to know the Koji builds attached to # that advisory, then backward_rel would true. backward = str_to_bool(request.args.get('backward_rel')) if backward and story_node_story_flow['backward_label']: desired_siblings_label = story_node_story_flow['backward_label'] elif not backward and story_node_story_flow['forward_label']: desired_siblings_label = story_node_story_flow['forward_label'] else: raise ValidationError( 'Siblings cannot be determined on this kind of resource') sibling_nodes = story_manager.get_sibling_nodes(desired_siblings_label, story_node) # Inflating and formatting results from Neo4j serialized_results = [] for result in sibling_nodes: inflated_node = inflate_node(result[0]) serialized_node = inflated_node.serialized_all serialized_node['resource_type'] = inflated_node.__label__ serialized_node['display_name'] = inflated_node.display_name serialized_results.append(serialized_node) description = story_manager.get_siblings_description( story_node.display_name, story_node_story_flow, backward) result = {'data': serialized_results, 'meta': {'description': description}} return jsonify(result)