def mocked_invoker(**sysattrs): major = sysattrs.pop("major", 3) pyenv = sysattrs.pop("pyenv", None) use_path = sysattrs.pop("use_path", False) exe_exists = sysattrs.pop("exe_exists", True) sysattrs.setdefault("base_prefix", "/usr") sysattrs.setdefault("prefix", sysattrs["base_prefix"]) sysattrs["base_prefix"] = runez.resolved_path(sysattrs["base_prefix"]) sysattrs["prefix"] = runez.resolved_path(sysattrs["prefix"]) sysattrs.setdefault("executable", "%s/bin/python" % sysattrs["base_prefix"]) sysattrs.setdefault("version_info", (major, 7, 1)) sysattrs.setdefault("version", ".".join(str(s) for s in sysattrs["version_info"])) scanner = None if not pyenv else PythonInstallationScanner(pyenv) with patch("runez.pyenv.os.path.realpath", side_effect=lambda x: x): with patch("runez.pyenv.sys") as mocked: for k, v in sysattrs.items(): setattr(mocked, k, v) if isinstance(exe_exists, bool): with patch("runez.pyenv.is_executable", return_value=exe_exists): return PythonDepot(scanner=scanner, use_path=use_path) with patch("runez.pyenv.is_executable", side_effect=exe_exists): return PythonDepot(scanner=scanner, use_path=use_path)
def test_venv(temp_folder, logged): depot = PythonDepot(use_path=False) import sys p = depot.find_python(sys.executable) assert p is depot.invoker # Simulate an explicit reference to a venv python mk_python("8.6.1") mk_python("8.6.1", prefix="foo", base_prefix=".pyenv/versions/8.6.1", folder=".venv") assert str(depot) == "0 scanned" pvenv = depot.find_python(".venv/bin/python") assert str(depot) == "0 scanned" assert depot.find_python(".venv") == pvenv assert str(pvenv) == ".pyenv/versions/8.6.1 [cpython:8.6.1]" # Edge case: version is found via .pyenv first depot = PythonDepot(scanner=CustomScanner(".pyenv"), use_path=False) assert str(depot) == "1 scanned" pvenv = depot.find_python(".venv/bin/python") assert str(pvenv) == ".pyenv/versions/8.6.1 [cpython:8.6.1]" assert depot.scanned == [pvenv] p95 = depot.find_python("9.5.1") assert p95.problem is None assert str(depot) == "2 scanned" assert depot.scanned == [p95, pvenv] assert not logged
def test_depot_adhoc(temp_folder, monkeypatch): depot = PythonDepot(use_path=False) p11 = depot.find_python("11.0.0") pfoo = depot.find_python("/foo") assert p11.problem == "not available" # Only paths are cached p11b = depot.find_python("11.0.0") assert p11 is not p11b assert p11 == p11b assert depot.find_python("/foo") == pfoo # Edge case: check we can still compare incomplete objects (missing spec.version here for 'pfoo') assert pfoo == pfoo assert not (pfoo < pfoo) assert not (pfoo > pfoo) assert pfoo < p11 assert p11 > pfoo assert not (p11 < pfoo) assert not (pfoo > p11) # Edge case: comparison still works even when there is no spec, arbitrarily sort no spec lower... pfoo.spec = None assert pfoo < p11 mk_python("python", folder="some-path", version="11.0.0") py_path = os.path.realpath("some-path/bin/python") p11 = depot.find_python(py_path) assert depot.find_python("./some-path/") == p11 assert depot.find_python("some-path/bin") == p11 assert str(p11) == "some-path/bin/python [cpython:11.0.0]"
def test_invoker(): depot = PythonDepot(use_path=False) assert depot.find_python(None) is depot.invoker assert depot.find_python("") is depot.invoker assert depot.find_python("py") is depot.invoker assert depot.find_python("python") is depot.invoker assert depot.find_python(depot.invoker.executable) is depot.invoker assert depot.find_python("invoker") is depot.invoker assert depot.find_python("%s" % sys.version_info[0]) is depot.invoker assert "invoker" in str(depot.invoker) assert str(depot.invoker) == repr( depot.invoker) # Identical when coloring is off # Linux case with py3 depot = mocked_invoker() assert depot.invoker.executable == "/usr/bin/python3.7" assert depot.invoker.folder == runez.to_path("/usr/bin") assert depot.invoker.major == 3 # Linux case without py3 depot = mocked_invoker(major=2, base_prefix="/usr/local") assert depot.invoker.executable == "/usr/local/bin/python2.7" assert depot.invoker.major == 2 # Linux case with only /usr/bin/python depot = mocked_invoker( major=2, exe_exists=lambda x: "python2" not in x and "python3" not in x) assert depot.invoker.executable == "/usr/bin/python" assert depot.invoker.major == 2 # Use sys.executable when prefix can't be used to determine invoker depot = mocked_invoker(major=2, base_prefix=None, executable="/foo", exe_exists=False) assert depot.invoker.executable == "/foo" assert depot.invoker.major == 2 # macos silly path choices depot = mocked_invoker( major=2, base_prefix="/System/Library/Frameworks/Python.framework/Versions/2.7") assert depot.invoker.executable == "/usr/bin/python2" assert depot.invoker.major == 2 depot = mocked_invoker( base_prefix= "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7" ) assert depot.invoker.executable == "/usr/bin/python3" assert depot.invoker.major == 3 depot = mocked_invoker( base_prefix= "/usr/local/Cellar/[email protected]/3.7.1_1/Frameworks/Python.framework/Versions/3.7" ) assert depot.invoker.executable == "/usr/local/bin/python3" assert depot.invoker.major == 3
def test_unknown(): depot = PythonDepot(use_path=False) p = depot.find_python("foo") assert str(p) == "foo [not available]" assert p.executable == "foo" assert p.folder is None assert p.major is None assert p.problem == "not available" assert p.spec.canonical == "foo:" assert p.spec.text == "foo" assert p.major is None assert p.version is None
def cmd_diagnostics(): """Show system diagnostics sample""" parser = runez.cli.parser() parser.add_argument("--border", default="colon", choices=NAMED_BORDERS, help="Use custom border.") parser.add_argument("--pyenv", default="~/.pyenv", help="Pyenv folder to scan for python installations.") args = parser.parse_args() from runez.pyenv import PythonDepot, PythonInstallationScanner scanner = PythonInstallationScanner(args.pyenv) if args.pyenv else None depot = PythonDepot(scanner=scanner, use_path=True) available = depot.representation() print( PrettyTable.two_column_diagnostics(runez.SYS_INFO.diagnostics(), available, border=args.border))
def test_empty_depot(): depot = PythonDepot(use_path=False) assert str(depot) == "0 scanned" assert depot.from_path == [] assert depot.scanned == [] assert depot.find_python(depot.invoker) is depot.invoker assert depot.find_python(PythonSpec( depot.invoker.executable)) is depot.invoker assert depot.find_python(PythonSpec("invoker")) is depot.invoker assert depot.find_python("invoker") is depot.invoker assert depot.find_python(depot.invoker.spec.family) is depot.invoker assert depot.representation() == "" p = depot.find_python("foo") assert str(p) == "foo [not available]" assert repr(p) == "foo [not available]" assert p.problem == "not available" assert str(depot) == "0 scanned"
def test_sorting(temp_folder): mk_python("3.6.1") mk_python("3.7.2") mk_python("3.8.3") mk_python("conda-4.6.1") mk_python("miniconda3-4.3.2") depot = PythonDepot(scanner=PythonInstallationScanner(".pyenv"), use_path=False) assert str(depot) == "5 scanned" versions = [p.spec.canonical for p in depot.scanned] assert versions == [ "conda:4.6.1", "conda:4.3.2", "cpython:3.8.3", "cpython:3.7.2", "cpython:3.6.1" ]
def _diagnostics(self): yield "base", self.base_folder yield "invoker python", PythonDepot(use_path=False).invoker.representation()
def available_pythons(self): pyenv = self.pyenv() scanner = PythonInstallationScanner(pyenv) if pyenv else None return PythonDepot(scanner=scanner)
def test_depot(temp_folder, monkeypatch, logged): # Create some pyenv-style python installation mocks (using version 8 so it sorts above any real version...) mk_python("8.6.1") mk_python("8.7.2") runez.symlink("8.6.1", ".pyenv/versions/8.6", must_exist=False, logger=None) # Verify that if invoker is one of the pyenv-installations, it is properly detected depot = mocked_invoker(pyenv=".pyenv", base_prefix=".pyenv/versions/8.6.1", version_info=(8, 6, 1)) p8 = depot.find_python("8") p86 = depot.find_python("8.6") assert str( p8 ) == ".pyenv/versions/8.7.2 [cpython:8.7.2]" # Latest version 8 (invoker doesn't take precedence) assert p8.folder == runez.to_path(".pyenv/versions/8.7.2/bin").absolute() assert p86 is depot.invoker assert str(depot) == "2 scanned" assert depot.scanned == [p8, p86] assert depot.from_path == [] assert not logged mk_python("8.8.3", executable=False) mk_python("8.9.0") mk_python("miniconda3-4.7.12") # Create some PATH-style python installation mocks (using version 9 so it sorts higher than pyenv ones) mk_python("python", folder="path1", version="9.5.1") mk_python("python3", folder="path1", content=[ "foo" ]) # Invalid: mocked _pv.py does not return the right number of lines mk_python("python", folder="path2", content=["foo; bar"]) # --version fails mk_python("some-other-python-exe-name", folder="path3", version="8.5.0") mk_python("python3", folder="path3") # Invalid version with runez.CurrentFolder("path3/bin"): runez.symlink("some-other-python-exe-name", "python", logger=None) monkeypatch.setenv("PATH", "bar:path1/bin:path2/bin:path3/bin") scanner = PythonInstallationScanner(".pyenv") assert str(scanner) == "portable python [.pyenv]" depot = PythonDepot(scanner=scanner, use_path=True) assert str(depot) == "4 scanned, 2 from PATH" r = depot.representation() assert "Installed portable python:" in r assert "Available pythons from PATH:" in r depot.find_preferred_python("8.7.2,8.9.0", "8.7", "8.10") assert depot.preferred_python.version == "8.7.2" depot.find_preferred_python("8.7.2,8.9.0", "8.7", "8.8") assert depot.preferred_python.version == "8.9.0" assert depot.find_python(None) is depot.preferred_python assert depot.find_python("8") is depot.preferred_python depot.find_preferred_python("8.7.2,8.9.0", "10.7", "10.8") assert depot.preferred_python is None depot.find_preferred_python("") assert depot.preferred_python is None assert len(depot.from_path) == 2 assert len(depot.scanned) == 4 assert depot.scan_path_env_var( ) is None # Already scanned to try and find invoker p95 = depot.find_python("9.5.1") assert str(p95) == "path1/bin/python [cpython:9.5.1]" check_find_python(depot, "9", "path1/bin/python [cpython:9.5.1]") check_find_python(depot, "42.4", "42.4 [not available]") check_find_python(depot, "foo", "foo [not available]") check_find_python(depot, "python:43.0.0", "python:43.0.0 [not available]") with pytest.raises(runez.system.AbortException): depot.find_python("/bar", fatal=True) pbar = depot.find_python("/bar") assert str(pbar) == "/bar [not an executable]" assert pbar.problem assert not pbar.satisfies(depot.spec_from_text("python")) p8 = depot.find_python("8") p8a = depot.find_python(PythonSpec("8")) assert p8a is p8 p86 = depot.find_python("8.6") p87 = depot.find_python("8.7") p88 = depot.find_python("8.8") p89 = depot.find_python("8.9") c = depot.find_python("conda") c47 = depot.find_python("conda:4.7") assert c47 is c assert depot.find_python(PythonSpec("conda47")) is c47 assert depot.scanned == [p89, p87, p86, c47] assert p8.major == 8 assert p88.major == 8 assert str(p8) == ".pyenv/versions/8.9.0 [cpython:8.9.0]" assert str(p88) == "8.8 [not available]" assert str(c47) == ".pyenv/versions/miniconda3-4.7.12 [conda:4.7.12]" assert p8 is p89 assert p8 == p89 assert p8 != p88 assert p8 != pbar assert p8.satisfies(PythonSpec("python")) assert p8.satisfies(PythonSpec("python8")) assert p8.satisfies(PythonSpec("py8.9.0")) assert not p8.satisfies(PythonSpec("py8.9.1")) assert c47.satisfies(PythonSpec("conda47")) assert len({p8, p89}) == 1 assert len({p8, p89, p88}) == 2