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"]
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"]
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