Esempio n. 1
0
    def add_relationship(self,
                         entity1_ilx: str,
                         relationship_ilx: str,
                         entity2_ilx: str,
                         real_server_resp: bool = False) -> dict:
        """ Adds relationship connection in Interlex

        A relationship exists as 3 different parts:
            1. entity with type term, cde, fde, or pde
            2. entity with type relationship that connects entity1 to entity2
                -> Has its' own meta data, so no value needed
            3. entity with type term, cde, fde, or pde
        """
        entity1_ilx = self.get_ilx_fragment(entity1_ilx)
        relationship_ilx = self.get_ilx_fragment(relationship_ilx)
        entity2_ilx = self.get_ilx_fragment(entity2_ilx)
        data = {
            'term1_id': entity1_ilx,
            'relationship_tid': relationship_ilx,
            'term2_id': entity2_ilx
        }
        # resp = self._post('term/add-relationship', data={**data, **{'batch-elastic': 'true'}})
        resp = self._post('term/add-relationship', data=data)

        if resp.status_code == 200:
            log.warning(
                f"Relationship:"
                f" [{data['term1_id']}] -> [{data['relationship_tid']}] -> [{data['term2_id']}]"
                f" has already been added")
        # if real_server_resp:
        data = resp.json()['data']
        return data
Esempio n. 2
0
    def withdraw_relationship(self, entity1_ilx: str, relationship_ilx: str,
                              entity2_ilx: str) -> dict:
        """ Adds relationship connection in Interlex

        A relationship exists as 3 different parts:
            1. entity with type term, cde, fde, or pde
            2. entity with type relationship that connects entity1 to entity2
                -> Has its' own meta data, so no value needed
            3. entity with type term, cde, fde, or pde
        """
        entity1_data = self.get_entity(entity1_ilx)
        if not entity1_data['id']:
            raise self.EntityDoesNotExistError(
                f'entity1_ilx: {entity1_data} does not exist')
        relationship_data = self.get_entity(relationship_ilx)
        if not relationship_data['id']:
            raise self.EntityDoesNotExistError(
                f'relationship_ilx: {relationship_ilx} does not exist')
        entity2_data = self.get_entity(entity2_ilx)
        if not entity2_data['id']:
            raise self.EntityDoesNotExistError(
                f'entity2_ilx: {entity2_data} does not exist')
        data = {
            'term1_id': entity1_data['id'],  # entity1_data['id'],
            'relationship_tid':
            relationship_data['id'],  # relationship_data['id'],
            'term2_id': entity2_data['id'],  # entity2_data['id'],
            'term1_version': entity1_data['version'],
            'term2_version': entity2_data['version'],
            'relationship_term_version': relationship_data['version'],
            'withdrawn': '1',
            # 'orig_uid': self.user_id,  # BUG: php lacks orig_uid update
        }
        entity_relationships = self.get_relationship_via_tid(
            entity1_data['id'])
        # TODO: parse through entity_relationships to see if we have a match; else print warning and return None
        relationship_id = None
        for relationship in entity_relationships:
            if str(relationship['term1_id']) == str(entity1_data['id']):
                if str(relationship['term2_id']) == str(entity2_data['id']):
                    if str(relationship['relationship_tid']) == str(
                            relationship_data['id']):
                        relationship_id = relationship['id']
                        break
        if not relationship_id:
            log.warning('Relationship you wanted to delete does not exist')
            return {}
        output = self._post(f'term/edit-relationship/{relationship_id}',
                            data={
                                **data,
                                **{
                                    'batch-elastic': 'true'
                                }
                            }).json()['data']
        return output
Esempio n. 3
0
    def setup(self, **kwargs):
        oq.OntCuries({'TMP': 'http://uri.interlex.org/base/tmp_'})

        if self.apiEndpoint is not None:
            try:
                self.ilx_cli = InterLexClient(base_url=self.apiEndpoint)
            except exc.NoApiKeyError:
                if not self.readonly:
                    # expect attribute errors for ilx_cli
                    log.warning(
                        'You have not set an API key for the SciCrunch API! '
                        'InterLexRemote will error if you try to use it.')

        super().setup(**kwargs)
Esempio n. 4
0
    def add_annotation(self,
                       term_ilx_id: str,
                       annotation_type_ilx_id: str,
                       annotation_value: str,
                       real_server_resp: bool = False) -> dict:
        """ Adding an annotation value to a prexisting entity

            An annotation exists as 3 different parts ([1] -> [2] -> [3]):
                1. entity with type term, TermSet, cde, fde, or pde
                2. entity with type annotation
                3. string value of the annotation

            :param term_ilx_id: Term ILX ID
            :param annotation_type_ilx_id: Annototation ILX ID
            :param annotation_value: Annotation value
            :param real_server_resp: Will return term IDs and versions, not InterLex IDs. 
            :return: Annotation Record

            >>> self.add_annotation(
                    term_ilx_id='ilx_0101431',  # brain ILX ID
                    annotation_type_ilx_id='ilx_0381360',  # hasDbXref ILX ID
                    annotation_value='http://neurolex.org/wiki/birnlex_796'  # any string value
                )
        """
        tid = self.get_ilx_fragment(term_ilx_id)
        annotation_tid = self.get_ilx_fragment(annotation_type_ilx_id)
        data = {
            'tid': tid,
            'annotation_tid': annotation_tid,
            'value': annotation_value
        }
        resp = self._post('term/add-annotation',
                          data={
                              **data,
                              **{
                                  'batch-elastic': 'true'
                              }
                          })
        if resp.status_code == 200:
            log.warning(
                f"Annotation: "
                f"[{data['tid']}] -> [{data['annotation_tid']}] -> [{data['value']}]"
                f"has already been added")
        if real_server_resp:
            data = resp.json()['data']
        return data
Esempio n. 5
0
    def withdraw_annotation(self, term_ilx_id: str,
                            annotation_type_ilx_id: str,
                            annotation_value: str) -> Optional[dict]:
        """ If annotation doesnt exist, add it

            :param term_ilx_id: Term ILX ID
            :param annotation_type_ilx_id: Annototation ILX ID
            :param annotation_value: Annotation value
            :return: Empty Annotation Record
        """
        term_data = self.get_entity(term_ilx_id)
        if not term_data['id']:
            raise self.EntityDoesNotExistError('term_ilx_id: ' + term_ilx_id +
                                               ' does not exist')
        anno_data = self.get_entity(annotation_type_ilx_id)
        if not anno_data['id']:
            raise self.EntityDoesNotExistError('annotation_type_ilx_id: ' +
                                               annotation_type_ilx_id +
                                               ' does not exist')
        entity_annotations = self.get_annotation_via_tid(term_data['id'])
        annotation_id = ''
        for annotation in entity_annotations:
            if str(annotation['tid']) == str(term_data['id']):
                if str(annotation['annotation_tid']) == str(anno_data['id']):
                    if str(annotation['value']) == str(annotation_value):
                        annotation_id = annotation['id']
                        break
        if not annotation_id:
            log.warning('Annotation you wanted to delete does not exist')
            return None
        data = {
            'tid': term_data['id'],  # for delete
            'annotation_tid': anno_data['id'],  # for delete
            'value': annotation_value,  # for delete
            'term_version': term_data['version'],
            'annotation_term_version': anno_data['version'],
            'withdrawn': '1',
        }
        output = self._post(f"term/edit-annotation/{annotation_id}",
                            data=data).json()['data']
        return output
Esempio n. 6
0
    def add_entity(self,
                   label: str,
                   type: str,
                   cid: str = None,
                   definition: str = None,
                   comment: str = None,
                   superclass: str = None,
                   synonyms: list = None,
                   existing_ids: list = None,
                   force: bool = False,
                   **kwargs) -> dict:
        """ Add Interlex entity into SciCrunch.

            Loosely structured ontological data based on the source ontologies for readability.

        :param label: Preferred name of entity.
        :param type: Any of the following: term, TermSet, cde, pde, fde, relationship, annotation.
        # todo add description of each type here
        :param cid: Community ID
        :param definition: Entities official definition.
        :param comment: A foot note regarding either the interpretation of the data or the data itself
        :param superclass: The ilx_id of the parent of this entity. Example: Organ is a superclass to Brain
        :param synonyms: Alternate names of the label.
        :param existing_ids: Alternate/source ontological iri/curies. Can only be one preferred ID.
        :param force: If entity is different from existing entity. This will add it if you have admin privileges.
        :param kwargs: a net for extra unneeded paramters.
        :return: requests.Response of insert or query from existing.

        >>> self.add_entity( \
                label='Brain', \
                type='term',  # options: term, pde, fde, cde, annotation, or relationship \
                definition='Official definition for entity.', \
                comment='Additional casual notes for the next person.', \
                superclass='ilx_1234567', \
                synonyms=[{ \
                    'literal': 'Brains',  # label of synonym \
                    'type': 'obo:hasExactSynonym',  # Often predicate defined in ref ontology. \
                }], \
                existing_ids=[{ \
                    'iri': 'http://purl.obolibrary.org/obo/UBERON_0000955', \
                    'curie': 'UBERON:0000955',  # Obeys prefix:id structure. \
                    'preferred': '1',  # Can be 0 or 1 with a type of either str or int. \
                }], \
            )
        """
        synonyms = synonyms or []
        existing_ids = existing_ids or []
        entity = {
            'label':
            self._process_field(label, accepted_types=(str, )),
            'type':
            self._process_field(type,
                                accepted_values=self.entity_types,
                                accepted_types=(str, )),
            'cid':
            self._process_field(cid, accepted_types=(str, int)),
            'definition':
            self._process_field(definition, accepted_types=(str, )),
            'comment':
            self._process_field(comment, accepted_types=(str, )),
            'superclasses':
            self._process_superclass(superclass),
            'synonyms':
            self._process_synonyms(synonyms),
            'existing_ids':
            self._process_existing_ids(existing_ids),
            'force':
            force,
        }
        #entity['batch-elastic'] = 'true'
        resp = self._post('term/add', data=deepcopy(entity))
        entity = resp.json()['data']
        if resp.status_code == 200:
            log.warning(
                f"You already added {entity['label']} with InterLex ID {entity['ilx']}"
            )
        # Backend handles more than one. User doesn't need to know.
        entity['superclass'] = entity.pop('superclasses')
        if entity['superclass']:
            entity['superclass'] = 'http://uri.interlex.org/base/' + entity[
                'superclass'][0]['ilx']
        # todo: match structure of input
        # todo: compare if identical; for now test_interlex_client will do
        return entity
Esempio n. 7
0
    def _scicrunch_api_query(self, kwargs, iri, curie, label, term, predicates,
                             limit):
        resp = None
        if iri:
            try:
                resp: dict = self.ilx_cli.get_entity(iri)
            except:
                pass

        if resp is None and curie:
            try:
                resp: dict = self.ilx_cli.get_entity_from_curie(curie)
            except (requests.exceptions.HTTPError, self.ilx_cli.Error) as e:
                log.debug(e)
                resp = None

            if resp is None or resp[
                    'id'] is None:  # FIXME should error before we have to check this
                # sometimes a remote curie does not match ours
                try:
                    resp: dict = self.ilx_cli.get_entity_from_curie(
                        self.OntId(iri).curie)
                except (requests.exceptions.HTTPError,
                        self.ilx_cli.Error) as e:
                    log.debug(e)
                    return

                if resp['id'] is None:
                    return

        elif label:
            try:
                resp: list = self.ilx_cli.query_elastic(label=label,
                                                        size=limit)
            except (requests.exceptions.HTTPError, self.ilx_cli.Error) as e:
                log.debug(e)
                resp = None
        elif term:
            try:
                resp: list = self.ilx_cli.query_elastic(term=term, size=limit)
            except (requests.exceptions.HTTPError, self.ilx_cli.Error) as e:
                log.debug(e)
                resp = None
        else:
            pass  # querying on iri or curie through ilx cli is ok

        if not resp:
            return

        resps = [resp] if isinstance(resp, dict) else resp

        # FIXME this is really a temp hack until we can get the
        # next version of the alt resolver up and running with
        # since iirc it can resolve curies
        for resp in resps:
            _frag = resp['ilx']
            if _frag is None:
                log.warning(resp)
                continue

            _ilx = 'http://uri.interlex.org/base/' + _frag
            if iri is None:
                _iri = _ilx
            else:
                _iri = iri

            if curie is None:
                _curie = self._fix_fragment(resp['ilx'])
            else:
                _curie = curie

            # TODO if predicates is not None then need calls to get annotations and relations
            predicates = self._proc_api(resp)
            yield self.QueryResult(
                query_args=kwargs,
                iri=_iri,
                curie=_curie,
                label=resp['label'],
                labels=tuple(),
                # abbrev=None,  # TODO
                # acronym=None,  # TODO
                definition=resp['definition'],
                synonyms=tuple(resp['synonyms']),
                # deprecated=None,
                # prefix=None,
                # category=None,
                predicates=predicates,
                # _graph=None,
                _blob=resp,
                source=self,
            )
Esempio n. 8
0
    def _rcall__(
        self,
        term=None,  # put this first so that the happy path query('brain') can be used, matches synonyms
        prefix=tuple(),  # limit search within these prefixes
        category=None,  # like prefix but works on predefined categories of things like 'anatomical entity' or 'species'
        label=None,  # exact matches only
        abbrev=None,  # alternately `abbr` as you have
        search=None,  # hits a lucene index, not very high quality
        suffix=None,  # suffix is 1234567 in PREFIX:1234567
        curie=None,  # if you are querying you can probably just use OntTerm directly and it will error when it tries to look up
        iri=None,  # the most important one
        predicates=tuple(
        ),  # provided with an iri or a curie to extract more specific triple information
        exclude_prefix=tuple(),
        depth=1,
        direction='OUTGOING',
        limit=10,
        include_deprecated=False,
        include_supers=False,
        include_all_services=False,
        raw=False,
    ):
        prefix = one_or_many(prefix) + self._prefix
        category = one_or_many(category) + self._category
        qualifiers = cullNone(
            prefix=prefix if prefix else None,
            exclude_prefix=exclude_prefix if exclude_prefix else None,
            category=category if category else None)
        queries = cullNone(abbrev=abbrev,
                           label=label,
                           term=term,
                           search=search)
        graph_queries = cullNone(
            predicates=tuple(
                self._OntId(p) if ':' in p else p
                for p in predicates),  # size must be known no generator
            depth=depth,
            direction=direction)
        identifiers = cullNone(suffix=suffix, curie=curie, iri=iri)
        control = dict(include_deprecated=include_deprecated,
                       include_supers=include_supers,
                       limit=limit)
        if queries and identifiers:
            log.warning(
                f'\x1b[91mWARNING: An identifier ({list(identifiers)}) was supplied. Ignoring other query parameters {list(queries)}.\x1b[0m'
            )
            queries = {}
        if 'suffix' in identifiers and 'prefix' not in qualifiers:
            raise ValueError(
                'Queries using suffix= must also include an explicit prefix.')
        if len(queries) > 1:
            raise ValueError(
                'Queries only accept a single non-qualifier argument. '
                'Qualifiers are prefix=, category=.')
        # TODO more conditions here...

        # TODO? this is one place we could normalize queries as well instead of having
        # to do it for every single OntService
        kwargs = {
            **qualifiers,
            **queries,
            **graph_queries,
            **identifiers,
            **control
        }
        for j, service in enumerate(self.services):
            # TODO query keyword precedence if there is more than one
            #print(red.format(str(kwargs)))
            # TODO don't pass empty kwargs to services that can't handle them?
            for i, result in enumerate(service.query(**kwargs)):
                #print(red.format('AAAAAAAAAA'), result)
                if result:
                    yield result if raw else result.asTerm()
                    if search is None and term is None and result.label and not include_all_services:
                        return  # FIXME order services based on which you want first for now, will work on merging later
Esempio n. 9
0
    def _graphQuery(self,
                    subject,
                    predicate,
                    depth=1,
                    direction='OUTGOING',
                    entail=True,
                    inverse=False,
                    include_supers=False,
                    done=None):
        # TODO need predicate mapping... also subClassOf inverse?? hasSubClass??
        # TODO how to handle depth? this is not an obvious or friendly way to do this
        if entail and inverse:
            raise NotImplementedError(
                'Currently cannot handle inverse and entail at the same time.')

        if include_supers:
            if done is None:
                done = set()

            done.add(subject)
            for _, sup in self._graphQuery(subject, 'subClassOf', depth=40):
                if sup not in done:
                    done.add(sup)
                    for p, o in self._graphQuery(sup,
                                                 predicate,
                                                 depth=depth,
                                                 direction=direction,
                                                 entail=entail,
                                                 inverse=inverse,
                                                 done=done):
                        if o not in done:
                            done.add(o)
                            yield p, o

            for p, o in self._graphQuery(subject,
                                         predicate,
                                         depth=depth,
                                         direction=direction,
                                         entail=entail,
                                         inverse=inverse,
                                         done=done):
                if o not in done:
                    yield p, o
                    done.add(o)
                    yield from self._graphQuery(o,
                                                predicate,
                                                depth=depth,
                                                direction=direction,
                                                entail=entail,
                                                inverse=inverse,
                                                include_supers=include_supers,
                                                done=done)
            return

        d_nodes_edges = self.sgg.getNeighbors(subject,
                                              relationshipType=predicate,
                                              depth=depth,
                                              direction=direction,
                                              entail=entail)  # TODO

        if d_nodes_edges:
            edges = d_nodes_edges['edges']
        else:
            if inverse:  # it is probably a bad idea to try to be clever here AND INDEED IT HAS BEEN
                predicate = self.inverses[predicate]

            _p = (predicate.curie if hasattr(predicate, 'curie')
                  and predicate.curie is not None else predicate)
            log.warning(f'{subject.curie} has no edges with predicate {_p} ')
            return

        s, o = 'sub', 'obj'
        if inverse:
            s, o = o, s

        def properPredicate(e):
            if ':' in e['pred']:
                p = self.OntId(e['pred'])
                if inverse:  # FIXME p == predicate ? no it is worse ...
                    p = self.inverses[p]

                p = p.curie
            else:
                p = e['pred']

            return p

        if depth > 1:
            #subjects = set(subject.curie)
            seen = {((predicate.curie if isinstance(predicate, self.OntId) else
                      predicate), subject.curie)}
            for i, e in enumerate(
                    self.sgg.ordered(subject.curie, edges, inverse=inverse)):
                if [
                        v for k, v in e.items()
                        if k != 'meta' and v.startswith('_:')
                ]:
                    # FIXME warn on these ? bnode getting pulled in ...
                    # argh ... this is annoying to deal with
                    continue
                #print('record number:', i)  # FIXME
                # FIXME need to actually get the transitive closure, this doesn't actually work
                #if e[s] in subjects:
                #subjects.add(object.curie)
                object = e[o]
                p = e['pred']  # required if entail == True
                if (p, object) not in seen:
                    # to make OntTerm(object) work we need to be able to use the 'meta' section...
                    # and would have to fetch the object directly anyway since OntTerm requires
                    # direct atestation ... which suggests that we probably need/want a bulk constructor
                    seen.add((p, object))
                    yield (
                        properPredicate(e), self.OntId(object)
                    )  # FIXME TODO this is _very_ inefficient for multiple lookups...

        else:
            _has_part_list = ['http://purl.obolibrary.org/obo/BFO_0000051']
            _disjoint_with_list = ['disjointWith']
            scurie = self._remote_curies.qname(subject)
            pred_objects = (
                (properPredicate(e), self.OntId(e[o])) for e in edges
                if e[s] == scurie
                #and not print(predicate, scurie, e['pred'], e[o])
                and not [
                    v
                    for k, v in e.items() if k != 'meta' and v.startswith('_:')
                ] and ('owlType' not in e['meta'] or
                       (e['meta']['owlType'] != _has_part_list
                        and e['meta']['owlType'] != _disjoint_with_list)))
            yield from pred_objects