def check_installed(name: str = __package__) -> bool: try: from importlib.metadata import Distribution, PackageNotFoundError # noqa except ImportError: try: from importlib_metadata import Distribution, PackageNotFoundError # noqa except ImportError: # Should not get here EVER, but just in case lets just try checking pip freeze instead result, output = subprocess.getstatusoutput( [f"{sys.executable} -m pip freeze"]) if result != 0: # Not Ok return False is_installed = name in [ requirement.split("==")[0] for requirement in output.splitlines() ] if name != "sickchill" or (name == "sickchill" and is_installed): logger.debug(f"{name} installed: {is_installed}") return is_installed try: Distribution.from_name(name) logger.debug(f"{name} installed: True") except PackageNotFoundError: if name != "sickchill": logger.debug(f"{name} installed: False") return False return True
def test_package_not_found_mentions_metadata(self): # When a package is not found, that could indicate that the # packgae is not installed or that it is installed without # metadata. Ensure the exception mentions metadata to help # guide users toward the cause. See #124. with self.assertRaises(PackageNotFoundError) as ctx: Distribution.from_name('does-not-exist') assert "metadata" in str(ctx.exception)
def check_installed(): try: from importlib.metadata import Distribution, PackageNotFoundError except ImportError: from importlib_metadata import Distribution, PackageNotFoundError try: Distribution.from_name('sickchill') except PackageNotFoundError: return False return True
def test_coherence_of_get_modules() -> None: for package in PACKAGES: dist = Distribution.from_name(package) lhs = _get_modules_by_toplevel_txt(dist) rhs = _get_modules_by_files(dist) if lhs is not None: assert set(lhs) == set(rhs)
def get_pkg_paths(pkg: Distribution) -> t.List[Path]: # Some egg-info packages have e.g. src/ paths in their SOURCES.txt file, # but they also have this: mods = set((pkg.read_text("top_level.txt") or "").split()) if not mods and pkg.files: # Fall back to RECORD file for dist-info packages without top_level.txt mods = { f.parts[0] if len(f.parts) > 1 else f.with_suffix("").name for f in pkg.files if f.suffix == ".py" or Path(pkg.locate_file(f)).is_symlink() } if not mods: raise RuntimeError( f"Can’t determine top level packages of {pkg.metadata['Name']}" ) return [Path(pkg.locate_file(mod)) for mod in mods]
def test_more_complex_deps_requires_text(self): requires = textwrap.dedent(""" dep1 dep2 [:python_version < "3"] dep3 [extra1] dep4 [extra2:python_version < "3"] dep5 """) deps = sorted(Distribution._deps_from_requires_text(requires)) expected = [ 'dep1', 'dep2', 'dep3; python_version < "3"', 'dep4; extra == "extra1"', 'dep5; (python_version < "3") and extra == "extra2"', ] # It's important that the environment marker expression be # wrapped in parentheses to avoid the following 'and' binding more # tightly than some other part of the environment expression. assert deps == expected
def get_installed_packages(): """ get a list of all packages currently installed in the active environment """ packages = [] pool = Pool(4) # for dist in track( # list(Distribution.discover()), description="[cyan]Grabbing dependency info" # ): # packages.append(Package.from_dist(dist)) dists = list(Distribution.discover()) dists_num = len(dists) log.info("[bold]Found a total of {} distributions".format(dists_num), extra={"markup": True}) for package_enum in enumerate(pool.imap(Package.from_dist, dists), start=1): package = package_enum[1] log.info("{0}/{1}: processed [bold cyan]{2} {3}[/bold cyan]".format( package_enum[0], dists_num, package.name, package.version), extra={"markup": True}) packages.append(package) return packages
def __init__(self, config: ChrispileConfig): pkg = Distribution.from_name(__package__) eps = [ep for ep in pkg.entry_points if ep.group == 'console_scripts'] self.program_name = eps[0].name jinja_env = Environment(loader=PackageLoader(__package__, 'templates')) self.template = jinja_env.get_template('exec.sh') self.config = config
def test_find_distributions_specified_path(self): dists = itertools.chain.from_iterable( resolver(path=[str(self.site_dir)]) for resolver in Distribution._discover_resolvers() ) assert any( dist.metadata['Name'] == 'distinfo-pkg' for dist in dists )
def _find_entry_point(self, app: str) -> Optional[EntryPoint]: if not self.python_path.exists(): return None dists = Distribution.discover( name=self.main_package_name, path=[str(get_site_packages(self.python_path))]) for dist in dists: for ep in dist.entry_points: if ep.group == "pipx.run" and ep.name == app: return ep return None
def get_apps(dist: metadata.Distribution, bin_path: Path) -> List[str]: apps = set() sections = {"console_scripts", "gui_scripts"} # "entry_points" entry in setup.py are found here for ep in dist.entry_points: if ep.group not in sections: continue if (bin_path / ep.name).exists(): apps.add(ep.name) if WINDOWS and (bin_path / (ep.name + ".exe")).exists(): # WINDOWS adds .exe to entry_point name apps.add(ep.name + ".exe") # search installed files # "scripts" entry in setup.py is found here (test w/ awscli) for path in dist.files or []: # vast speedup by ignoring all paths not above distribution root dir # (venv/bin or venv/Scripts is above distribution root) if Path(path).parts[0] != "..": continue dist_file_path = Path(dist.locate_file(path)) try: if dist_file_path.parent.samefile(bin_path): apps.add(path.name) except FileNotFoundError: pass # not sure what is found here inst_files = dist.read_text("installed-files.txt") or "" for line in inst_files.splitlines(): entry = line.split(",")[0] # noqa: T484 inst_file_path = Path(dist.locate_file(entry)).resolve() try: if inst_file_path.parent.samefile(bin_path): apps.add(inst_file_path.name) except FileNotFoundError: pass return sorted(apps)
def from_dist(cls, dist: Distribution) -> "Requirement": direct_url_json = dist.read_text("direct_url.json") if direct_url_json is not None: direct_url = json.loads(direct_url_json) data = { "name": dist.metadata["Name"], "url": direct_url.get("url"), "editable": direct_url.get("dir_info", {}).get("editable"), "subdirectory": direct_url.get("subdirectory"), } if "vcs_info" in direct_url: vcs_info = direct_url["vcs_info"] data.update( url=f"{vcs_info['vcs']}+{direct_url['url']}", ref=vcs_info.get("requested_revision"), revision=vcs_info.get("commit_id"), ) return VcsRequirement.create(**data) return FileRequirement.create(**data) return NamedRequirement.create(name=dist.metadata["Name"], version=f"=={dist.version}")
def test_retrieves_version_of_self(self): dist = Distribution.from_name('distinfo-pkg') assert isinstance(dist.version, str) assert re.match(self.version_pattern, dist.version)
def create_production_scripts(self, tool, venv_session): """Create Rez production used binary scripts The binary script will be executed with Python interpreter flag -E, which will ignore all PYTHON* env vars, e.g. PYTHONPATH and PYTHONHOME. """ _log.info("Generating production scripts..") site_packages = venv_session.creator.purelib bin_path = venv_session.creator.bin_dir if tool.edit: egg_link = site_packages / ("%s.egg-link" % tool.name) if not egg_link.is_file(): _log.error("Tool %r installed in edit mode, but unable " "to find egg-link for generating production " "scripts from source. File not exists: %s" % (tool.name, egg_link)) return with open(str(egg_link), "r") as f: package_location = f.readline().strip() path = [str(package_location)] else: path = [str(site_packages)] dists = Distribution.discover(name=tool.name, path=path) specifications = { ep.name: "{ep.name} = {ep.value}".format(ep=ep) for dist in dists for ep in dist.entry_points if ep.group == "console_scripts" } # delete bin files written into virtualenv # this also avoided naming conflict between script 'rez' and dir 'rez' for script_name in specifications.keys(): script_path = bin_path / script_name if script_path.is_file(): os.remove(str(script_path)) venv_name = tool.name if tool.isolation else "rez" prod_bin_path = self._revision.production_bin_dir(venv_name) makedirs(prod_bin_path) maker = ScriptMaker(source_dir=None, target_dir=str(prod_bin_path)) maker.executable = str(venv_session.creator.exe) # Align with wheel # # Ensure we don't generate any variants for scripts because this is # almost never what somebody wants. # See https://bitbucket.org/pypa/distlib/issue/35/ maker.variants = {""} # Ensure old scripts are overwritten. # See https://github.com/pypa/pip/issues/1800 maker.clobber = True # This is required because otherwise distlib creates scripts that are # not executable. # See https://bitbucket.org/pypa/distlib/issue/32/ maker.set_mode = True if self._rez_in_edit: # Allow pre-caching rez_bin_path on script entry if environ var # `REZUP_EDIT_IN_PRODUCTION` is set with non-empty value. # See https://github.com/davidlatwe/rezup/pull/56 maker.script_template = r'''# -*- coding: utf-8 -*- import re import os import sys from %(module)s import %(import_name)s if os.getenv("REZUP_EDIT_IN_PRODUCTION"): from rez.system import system setattr(system, 'rez_bin_path', r'{rez_bin_path}') if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(%(func)s()) '''.format(rez_bin_path=str(prod_bin_path)) scripts = maker.make_multiple( specifications=specifications.values(), options=dict(interpreter_args=list(tool.flags))) return scripts
def test_distribution_at_str(self): dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info' dist = Distribution.at(str(dist_info_path)) assert dist.version == '1.0.0'
def test_distribution_at_pathlib(self): """Demonstrate how to load metadata direct from a directory.""" dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info' dist = Distribution.at(dist_info_path) assert dist.version == '1.0.0'
def test_find_distributions_specified_path(self): dists = Distribution.discover(path=[str(self.site_dir)]) assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists)
def test_invalid_inputs_to_from_name(self, name): with self.assertRaises(Exception): Distribution.from_name(name)
def _get_modules_by_toplevel_txt(dist: Distribution) -> Optional[List[str]]: modules = dist.read_text("top_level.txt") if modules is None: return None else: return modules.rstrip().split("\n")
def _get_modules(project_name: str) -> List[str]: dist = Distribution.from_name(project_name) return _get_modules_by_toplevel_txt(dist) or _get_modules_by_files(dist)
def list_engines(): entrypoints = ( entry_point for entry_point in Distribution.from_name("xarray").entry_points if entry_point.module == "xarray.backends") return build_engines(entrypoints)
def test_for_name_does_not_exist(self): with self.assertRaises(PackageNotFoundError): Distribution.from_name('does-not-exist')
def __init__(self, metadata, *args, **kwargs): Distribution.__init__(self, *args, **kwargs) self._data = metadata
def __init__(cls, name, bases, d): if 'PACKAGE' in d: # interrogate setup.py to automatically fill in some # class attributes for the subclass autofill = [ 'AUTHORS', 'DESCRIPTION', 'LICENSE', 'DOCUMENTATION', 'VERSION' ] for attr in autofill: if attr in d: raise ValueError( 'Do not manually set value value for ' f'"{attr}" when "PACKAGE={d["PACKAGE"]}" is declared') pkg = Distribution.from_name(d['PACKAGE']) setup = pkg.metadata if 'TITLE' not in d: cls.TITLE = setup['name'] d['TITLE'] = cls.TITLE cls.AUTHORS = f'{setup["author"]} <{setup["author-email"]}>' d['AUTHORS'] = cls.AUTHORS cls.DESCRIPTION = setup['summary'] d['DESCRIPTION'] = cls.DESCRIPTION cls.LICENSE = setup['license'] d['LICENSE'] = cls.LICENSE cls.DOCUMENTATION = setup['home-page'] d['DOCUMENTATION'] = cls.DOCUMENTATION cls.VERSION = setup['version'] d['VERSION'] = cls.VERSION if 'SELFEXEC' not in d: eps = [ ep for ep in pkg.entry_points if ep.group == 'console_scripts' ] if eps: if len(eps) > 1: # multiple console_scripts found but maybe # they're just the same thing different_scripts = [ ep for ep in eps if ep.value != eps[0].value ] if different_scripts: raise ValueError( 'SELFEXEC not defined and more than one ' 'console_scripts found') cls.SELFEXEC = eps[0].name d['SELFEXEC'] = cls.SELFEXEC cls.EXECSHELL = sys.executable d['EXECSHELL'] = cls.EXECSHELL script_location = shutil.which(cls.SELFEXEC) if not script_location: raise EnvironmentError( cls.SELFEXEC + ' not found in PATH - check your SELFEXEC') cls.SELFPATH = os.path.dirname(script_location) d['SELFPATH'] = cls.SELFPATH script_location = os.path.join(cls.SELFPATH, cls.SELFEXEC) if not os.path.isfile(script_location): raise EnvironmentError( script_location + ' not found - check your SELFPATH, SELFEXEC') # class variables to be enforced in the subclasses attrs = [ 'DESCRIPTION', 'TYPE', 'TITLE', 'LICENSE', 'AUTHORS', 'VERSION', 'SELFPATH', 'SELFEXEC', 'EXECSHELL' ] for attr in attrs: if attr not in d: raise ValueError( f"Class {name} doesn't define {attr} class variable") if type(d[attr]) is not str: raise ValueError(f'{attr} ({type(attr)}) must be a string') if 'OUTPUT_META_DICT' not in d: raise ValueError(f"Class {name} doesn't define OUTPUT_META_DICT") if type(d['OUTPUT_META_DICT']) is not dict: raise ValueError('OUTPUT_META_DICT must be dict') type.__init__(cls, name, bases, d)
def _mock_importlib_distributions(): return (Distribution.at(p) for p in WHEEL_BASE.glob("*.dist-info") ) # type: ignore[union-attr]