import code import collections class SubInterpreterExit(SystemExit): pass def fuzzy_match(string, collection, thing_being_matched): if len(collection) < 1: raise ValueError("No valid candidates to match!?") matches = collection if string: if string in collection: return string matches = [x for x in collection if string in x] if not matches: raise ValueError("No matches for %s (%s), possible valid values are: %s" % ( string, thing_being_matched, sorted(collection))) if string is not None and len(matches) > 1: matches = [x for x in collection if x.starts(string)] if len(matches) > 1: if string is None: raise ValueError("More than one item and no search criteria for %s, possible valid values are: %s" % ( thing_being_matched, sorted(matches))) raise ValueError("Too many matches for %s (%s), matching candidates are: %s" % ( string, thing_being_matched, sorted(matches))) return next(iter(matches)) class ConsoleUtils(object): def __init__(self, britney): self._britney = britney self._pkgid_cache = collections.defaultdict(list) self._build_cache() def _build_cache(self): for pkg_id in self._britney.all_binaries: self._pkgid_cache[pkg_id.package_name].append(pkg_id) def pkg_id(self, package_name, package_version=None, package_architecture=None, *, fuzzy_match_version=True, fuzzy_match_arch=True): """Look up BinaryPackageID Example: pkg_id("lintian", "2.5", "amd64") -> BinaryPackageID("lintian", "2.5.10", "amd64") (Note that difference in version is intentional and a part of the fuzzy matching) :param package_name: Name of the package (e.g. "lintian") :param package_version: Version of the package (e.g. "2.5") :param package_architecture: Architecture of the package (note arch:all packages are always split in to a per-architecture package) :param fuzzy_match_version: If true, any version string is accepted as long as it uniquely identified the package. :param fuzzy_match_arch: If true, any architecture string is accepted as long as it uniquely identified the package. :return: Exactly one BinaryPackageId """ if package_name not in self._pkgid_cache: raise ValueError("Unknown package name: %s" % package_name) package_candidates = self._pkgid_cache[package_name] unique_versions = {x.version for x in package_candidates} real_version = package_version if package_version is None or package_version not in unique_versions: if not fuzzy_match_version: if unique_versions is None: raise ValueError("unique_versions cannot be None when fuzzy_match_version is False") raise ValueError("Unknown version %s, valid options are: %s" % ( package_version, sorted(unique_versions))) real_version = fuzzy_match(package_architecture, unique_versions, 'package_version') unique_architectures = {x.architecture for x in package_candidates if x.version == real_version} real_architecture = package_architecture if package_architecture is None or package_architecture not in unique_architectures: if not fuzzy_match_arch: if package_architecture is None: raise ValueError("package_architecture cannot be None when fuzzy_match_arch is False") raise ValueError("Unknown architecture %s" % package_architecture) real_architecture = fuzzy_match(package_architecture, unique_architectures, 'package_architecture') match = [x for x in package_candidates if x.version == real_version and x.architecture == real_architecture] if len(match) != 1: if not match: raise ValueError("Package %s, version %s (%s) is not available on architecture %s (%s)" % package_name, package_version, real_version, package_architecture, real_architecture) raise ValueError("The terms %s, %s (%s) and %s (%s) did not result in a unique package!? All matches: %s" % package_name, package_version, real_version, package_architecture, real_architecture, sorted(match)) return match[0] def console_quit(): raise SubInterpreterExit() def run_python_console(britney_obj): console_utils = ConsoleUtils(britney_obj) console_locals = { 'britney': britney_obj, '__name__': '__console__', '__doc__': None, 'all_bin_pkg_ids': britney_obj.all_binaries.keys(), 'pkg_id': console_utils.pkg_id, 'quit': console_quit, 'exit': console_quit, } console = code.InteractiveConsole(locals=console_locals) banner = """\ Interactive python (REPL) shell in britney. Locals available * britney: Instance of the Britney object. * all_bin_pkg_ids: Set of all BinaryPackageIDs * pkg_id: Lookup a BinaryPackageID * quit()/exit(): leave this REPL console. """ try: console.interact(banner=banner, exitmsg='') except SubInterpreterExit: pass