예제 #1
0
def test_find_related():
    #
    #  r0
    #   | uses
    #   v
    #   r1
    #   | version
    #   v
    #   r2
    #   /\
    #  /  \ derived
    # v   v
    # r3  r4
    #
    tmp_dir = scratch_path / "find_related"
    dmf = DMF(path=tmp_dir, create=True)
    r = [resource.Resource({"name": "r{}".format(i)}) for i in range(5)]
    # r3 <-- derived <-- r2 <-- version <-- r1
    cr = resource.create_relation  # shortcut
    cr(r[2], Predicates.derived, r[3])
    cr(r[1], Predicates.version, r[2])
    # r4 <-- derived <-- r2
    cr(r[2], Predicates.derived, r[4])
    # r0 -- Uses --> r1
    cr(r[0], Predicates.uses, r[1])
    # add to dmf
    for i in range(5):
        dmf.add(r[i])
    # outgoing from r0 should include 1,2,3,4
    names = []
    for d, rr, m in dmf.find_related(r[0], meta=["aliases"]):
        names.append(m["aliases"][0])
    names.sort()
    assert names == ["r1", "r2", "r3", "r4"]
    # incoming to r4 should include r0, r1, r2
    names = []
    for d, rr, m in dmf.find_related(r[4], meta=["aliases"], outgoing=False):
        names.append(m["aliases"][0])
    names.sort()
    assert names == ["r0", "r1", "r2"]
예제 #2
0
def test_circular():
    #
    # r0 -> derived -> r1 -> derived >- r2 -+
    #  ^                                    |
    #  +------------------------------------+
    #     uses
    tmp_dir = scratch_path / "circular"
    dmf = DMF(path=tmp_dir, create=True)
    r = [resource.Resource({"name": "r{}".format(i)}) for i in range(3)]
    resource.create_relation(r[0], Predicates.derived, r[1])
    resource.create_relation(r[1], Predicates.derived, r[2])
    resource.create_relation(r[2], Predicates.uses, r[0])
    for rr in r:
        dmf.add(rr)
    # outgoing from r0
    names = []
    for d, rr, m in dmf.find_related(r[0], meta=["aliases"]):
        names.append(m["aliases"][0])
    names.sort()
    assert names == ["r0", "r1", "r2"]
    # incoming to r1
    names = []
    for d, rr, m in dmf.find_related(r[0], meta=["aliases"], outgoing=False):
        names.append(m["aliases"][0])
    names.sort()
    assert names == ["r0", "r1", "r2"]
    # reducing depth shortens output
    names = []
    for d, rr, m in dmf.find_related(r[0], meta=["aliases"], maxdepth=2):
        names.append(m["aliases"][0])
    names.sort()
    assert names == ["r1", "r2"]
    names = []
    for d, rr, m in dmf.find_related(r[0], meta=["aliases"], maxdepth=1):
        names.append(m["aliases"][0])
    names.sort()
    assert names == ["r1"]
예제 #3
0
def related(identifier, direction, color, unicode):
    _log.info(f"related to resource id='{identifier}'")
    t = _cterm if color else _noterm
    dmf = DMF()
    try:
        resource.identifier_str(identifier, allow_prefix=True)
    except ValueError as err:
        click.echo(f"{err}")
        sys.exit(Code.INPUT_VALUE.value)
    _log.debug(f"begin: finding root resource {identifier}")
    rsrc_list = list(find_by_id(identifier, dmf=dmf))
    n = len(rsrc_list)
    if n > 1:
        click.echo(f"Too many resources matching `{identifier}`")
        sys.exit(Code.INPUT_VALUE)
    rsrc = rsrc_list[0]
    _log.debug(f"end: finding root resource {identifier}")
    # get related resources
    _log.debug(f"begin: finding related resources for {identifier}")
    outgoing = direction == "out"
    rr = list(
        dmf.find_related(rsrc, meta=["aliases", "type"], outgoing=outgoing))
    _log.debug(f"end: finding related resources for {identifier}")
    # stop if no relations
    if not rr:
        _log.warning(f"no resource related to {identifier}")
        click.echo(f"No relations for resource `{identifier}`")
        sys.exit(0)
    _log.info(f"got {len(rr)} related resources")
    # debugging
    if _log.isEnabledFor(logging.DEBUG):
        dbgtree = '\n'.join(['  ' + str(x) for x in rr])
        _log.debug(f"related resources:\n{dbgtree}")
    # extract uuids & determine common UUID prefix length
    uuids = [item[2][resource.Resource.ID_FIELD] for item in rr]
    pfx = util.uuid_prefix_len(uuids)
    # initialize queue with depth=1 items
    q = [item for item in rr if item[0] == 1]
    # print root resource
    print(_related_item(rsrc.id, rsrc.name, rsrc.type, pfx, t, unicode))
    # set up printing style
    if unicode:
        # connector chars
        vrt, vrd, relbow, relbow2, rarr = (
            '\u2502',
            '\u2506',
            '\u2514',
            '\u251C',
            '\u2500\u2500',
        )
        # relation prefix and arrow
        relpre, relarr = (
            ['\u2500\u25C0\u2500\u2524', '\u2524'][outgoing],
            ['\u2502', '\u251C\u2500\u25B6'][outgoing],
        )
    else:
        # connector chars
        vrt, vrd, relbow, relbow2, rarr = '|', '.', '+', '+', '--'
        # relation prefix and arrow
        relpre, relarr = ['<-[', '-['][outgoing], [']-', ']->'][outgoing]
    # create map of #items at each level, so we can easily
    # know when there are more at a given level, for drawing
    n_at_level = {0: 0}
    for item in rr:
        depth = item[0]
        if depth in n_at_level:
            n_at_level[depth] += 1
        else:
            n_at_level[depth] = 1
    # print tree
    while q:
        depth, rel, meta = q.pop()
        n_at_level[depth] -= 1
        indent = ''.join([
            f" {t.blue}{vrd if n_at_level[i - 1] else ' '}{t.normal} "
            for i in range(1, depth + 1)
        ])
        print(f"{indent} {t.blue}{vrt}{t.normal}")
        rstr = f"{t.blue}{relpre}{t.yellow}{rel.predicate}{t.blue}{relarr}{t.normal}"
        if meta["aliases"]:
            item_name = meta["aliases"][0]
        else:
            item_name = meta.get("desc", "-")
        istr = _related_item(meta[resource.Resource.ID_FIELD], item_name,
                             meta["type"], pfx, t, unicode)
        # determine correct connector (whether there is another one down the stack)
        elbow = relbow if (not q or q[-1][0] != depth) else relbow2
        print(f"{indent} {t.blue}{elbow}{rarr}{t.normal}{rstr} {istr}")
        new_rr = []
        for d2, rel2, _ in rr:
            if outgoing:
                is_same = rel2.subject == rel.object
            else:
                is_same = rel2.object == rel.subject
            if d2 == depth + 1 and is_same:
                q.append((d2, rel2, _))
            else:
                new_rr.append((d2, rel2, _))
        rr = new_rr