def test_graph_bad_version_to_dot(self): expected = ( ('towel-stuff', 'bacon', 'bacon (<=0.2)'), ('grammar', 'bacon', 'truffles (>=1.2)'), ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), ('banana', 'strawberry', 'strawberry (>=0.5)'), ) dists = [] for name in self.DISTROS_DIST + self.DISTROS_EGG + self.BAD_EGGS: dist = get_distribution(name, use_egg_info=True) self.assertNotEqual(dist, None) dists.append(dist) graph = depgraph.generate_graph(dists) buf = StringIO() depgraph.graph_to_dot(graph, buf) buf.seek(0) matches = [] lines = buf.readlines() for line in lines[1:-1]: # skip the first and the last lines if line[-1] == '\n': line = line[:-1] match = self.EDGE.match(line.strip()) self.assertIsNot(match, None) matches.append(match.groups()) self.checkLists(matches, expected)
def test_graph_disconnected_to_dot(self): dependencies_expected = ( ('towel-stuff', 'bacon', 'bacon (<=0.2)'), ('grammar', 'bacon', 'truffles (>=1.2)'), ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), ('banana', 'strawberry', 'strawberry (>=0.5)'), ) disconnected_expected = ('cheese', 'bacon', 'strawberry') dists = [] for name in self.DISTROS_DIST + self.DISTROS_EGG: dist = get_distribution(name, use_egg_info=True) self.assertNotEqual(dist, None) dists.append(dist) graph = depgraph.generate_graph(dists) buf = StringIO() depgraph.graph_to_dot(graph, buf, skip_disconnected=False) buf.seek(0) lines = buf.readlines() dependencies_lines = [] disconnected_lines = [] # First sort output lines into dependencies and disconnected lines. # We also skip the attribute lines, and don't include the "{" and "}" # lines. disconnected_active = False for line in lines[1:-1]: # Skip first and last line if line.startswith('subgraph disconnected'): disconnected_active = True continue if line.startswith('}') and disconnected_active: disconnected_active = False continue if disconnected_active: # Skip the 'label = "Disconnected"', etc. attribute lines. if ' = ' not in line: disconnected_lines.append(line) else: dependencies_lines.append(line) dependencies_matches = [] for line in dependencies_lines: if line[-1] == '\n': line = line[:-1] match = self.EDGE.match(line.strip()) self.assertIsNot(match, None) dependencies_matches.append(match.groups()) disconnected_matches = [] for line in disconnected_lines: if line[-1] == '\n': line = line[:-1] line = line.strip('"') disconnected_matches.append(line) self.checkLists(dependencies_matches, dependencies_expected) self.checkLists(disconnected_matches, disconnected_expected)
def test_repr(self): dists = [] for name in self.DISTROS_DIST + self.DISTROS_EGG + self.BAD_EGGS: dist = get_distribution(name, use_egg_info=True) self.assertNotEqual(dist, None) dists.append(dist) graph = depgraph.generate_graph(dists) self.assertTrue(repr(graph))
def _graph(dispatcher, args, **kw): name = args[1] dist = get_distribution(name, use_egg_info=True) if dist is None: logger.warning('Distribution not found.') return 1 else: dists = get_distributions(use_egg_info=True) graph = generate_graph(dists) print(graph.repr_node(dist))
def test_generate_graph_egg(self): dists = [] for name in self.DISTROS_DIST + self.DISTROS_EGG: dist = get_distribution(name, use_egg_info=True) self.assertNotEqual(dist, None) dists.append(dist) choxie, grammar, towel, bacon, banana, strawberry, cheese = dists graph = depgraph.generate_graph(dists) deps = [(x.name, y) for x, y in graph.adjacency_list[choxie]] self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps) self.assertIn(choxie, graph.reverse_list[towel]) self.checkLists(graph.missing[choxie], ['nut']) deps = [(x.name, y) for x, y in graph.adjacency_list[grammar]] self.checkLists([('bacon', 'truffles (>=1.2)')], deps) self.checkLists(graph.missing[grammar], []) self.assertIn(grammar, graph.reverse_list[bacon]) deps = [(x.name, y) for x, y in graph.adjacency_list[towel]] self.checkLists([('bacon', 'bacon (<=0.2)')], deps) self.checkLists(graph.missing[towel], []) self.assertIn(towel, graph.reverse_list[bacon]) deps = [(x.name, y) for x, y in graph.adjacency_list[bacon]] self.checkLists([], deps) self.checkLists(graph.missing[bacon], []) deps = [(x.name, y) for x, y in graph.adjacency_list[banana]] self.checkLists([('strawberry', 'strawberry (>=0.5)')], deps) self.checkLists(graph.missing[banana], []) self.assertIn(banana, graph.reverse_list[strawberry]) deps = [(x.name, y) for x, y in graph.adjacency_list[strawberry]] self.checkLists([], deps) self.checkLists(graph.missing[strawberry], []) deps = [(x.name, y) for x, y in graph.adjacency_list[cheese]] self.checkLists([], deps) self.checkLists(graph.missing[cheese], [])
def test_generate_graph(self): dists = [] for name in self.DISTROS_DIST: dist = get_distribution(name) self.assertNotEqual(dist, None) dists.append(dist) choxie, grammar, towel = dists graph = depgraph.generate_graph(dists) deps = [(x.name, y) for x, y in graph.adjacency_list[choxie]] self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps) self.assertIn(choxie, graph.reverse_list[towel]) self.checkLists(graph.missing[choxie], ['nut']) deps = [(x.name, y) for x, y in graph.adjacency_list[grammar]] self.checkLists([], deps) self.checkLists(graph.missing[grammar], ['truffles (>=1.2)']) deps = [(x.name, y) for x, y in graph.adjacency_list[towel]] self.checkLists([], deps) self.checkLists(graph.missing[towel], ['bacon (<=0.2)'])
def get_infos(requirements, index=None, installed=None, prefer_final=True): """Return the informations on what's going to be installed and upgraded. :param requirements: is a *string* containing the requirements for this project (for instance "FooBar 1.1" or "BarBaz (<1.2)") :param index: If an index is specified, use this one, otherwise, use :class index.ClientWrapper: to get project metadatas. :param installed: a list of already installed distributions. :param prefer_final: when picking up the releases, prefer a "final" one over a beta/alpha/etc one. The results are returned in a dict, containing all the operations needed to install the given requirements:: >>> get_install_info("FooBar (<=1.2)") {'install': [<FooBar 1.1>], 'remove': [], 'conflict': []} Conflict contains all the conflicting distributions, if there is a conflict. """ # this function does several things: # 1. get a release specified by the requirements # 2. gather its metadata, using setuptools compatibility if needed # 3. compare this tree with what is currently installed on the system, # return the requirements of what is missing # 4. do that recursively and merge back the results # 5. return a dict containing information about what is needed to install # or remove if not installed: logger.debug('Reading installed distributions') installed = list(get_distributions(use_egg_info=True)) infos = {'install': [], 'remove': [], 'conflict': []} # Is a compatible version of the project already installed ? predicate = get_version_predicate(requirements) found = False # check that the project isn't already installed for installed_project in installed: # is it a compatible project ? if predicate.name.lower() != installed_project.name.lower(): continue found = True logger.info('Found %r %s', installed_project.name, installed_project.version) # if we already have something installed, check it matches the # requirements if predicate.match(installed_project.version): return infos break if not found: logger.debug('Project not installed') if not index: index = wrapper.ClientWrapper() if not installed: installed = get_distributions(use_egg_info=True) # Get all the releases that match the requirements try: release = index.get_release(requirements) except (ReleaseNotFound, ProjectNotFound): raise InstallationException('Release not found: %r' % requirements) if release is None: logger.info('Could not find a matching project') return infos metadata = release.fetch_metadata() # we need to build setuptools deps if any if 'requires_dist' not in metadata: metadata['requires_dist'] = _get_setuptools_deps(release) # build the dependency graph with local and required dependencies dists = list(installed) dists.append(release) depgraph = generate_graph(dists) # Get what the missing deps are dists = depgraph.missing[release] if dists: logger.info("Missing dependencies found, retrieving metadata") # we have missing deps for dist in dists: _update_infos(infos, get_infos(dist, index, installed)) # Fill in the infos existing = [d for d in installed if d.name == release.name] if existing: infos['remove'].append(existing[0]) infos['conflict'].extend(depgraph.reverse_list[existing[0]]) infos['install'].append(release) return infos