def entry_points(self): metadata_json = self.dist_info.files.get("metadata.json") if metadata_json: metadata = runez.read_json(metadata_json, default={}) extensions = metadata.get("extensions") if isinstance(extensions, dict): commands = extensions.get("python.commands") if isinstance(commands, dict): wrap_console = commands.get("wrap_console") if wrap_console: runez.log.trace("Found %s entry points in metadata.json" % len(wrap_console)) return wrap_console entry_points_txt = self.dist_info.files.get("entry_points.txt") if entry_points_txt: metadata = runez.file.ini_to_dict(entry_points_txt) console_scripts = metadata.get("console_scripts") if console_scripts: runez.log.trace("Found %s entry points in entry_points.txt" % len(console_scripts)) return console_scripts if self.bin.files: runez.log.trace("Found %s bin/ scripts" % len(self.bin.files)) return self.bin.files or None
def test_pathlib(temp_folder): subfolder = Path("subfolder") assert runez.to_path(subfolder) is subfolder assert not subfolder.is_dir() runez.ensure_folder(subfolder) assert subfolder.is_dir() with pytest.raises(Exception): runez.to_path("foo bar", no_spaces=Exception) with runez.CurrentFolder(subfolder, anchor=True): path = Path("foo") assert runez.short(path) == "foo" assert runez.short(path.absolute()) == "foo" assert runez.resolved_path(path) assert runez.parent_folder(path) == os.path.join(temp_folder, "subfolder") assert runez.touch(path) == 1 assert runez.copy(path, Path("bar")) == 1 assert runez.copy(Path("bar"), Path("baz")) == 1 foo_json = Path("foo.json") runez.write(path, '{"a": "b"}') runez.symlink(path, foo_json) assert runez.read_json(foo_json) == {"a": "b"} assert list(runez.readlines(foo_json)) == ['{"a": "b"}'] assert runez.basename(foo_json.absolute()) == "foo"
def test_json(temp_folder, monkeypatch): assert runez.read_json(None) is None assert runez.represented_json(None) == "null\n" assert runez.represented_json([]) == "[]\n" assert runez.represented_json({}) == "{}\n" assert runez.represented_json("foo") == '"foo"\n' assert runez.represented_json({None: 2}, none=True) == '{\n "null": 2\n}\n' assert runez.represented_json({None: 2}, none="None") == '{\n "None": 2\n}\n' assert runez.represented_json({None: None}, none=True) == '{\n "null": null\n}\n' assert runez.represented_json({None: 1, "foo": None}, none="_null") == '{\n "_null": 1,\n "foo": null\n}\n' with pytest.raises(TypeError): # py3 stdlib can't sort with None key... runez.represented_json({None: 2, "foo": "bar"}, none=True) data = {"a": "x", "b": "y"} assert runez.represented_json(data) == '{\n "a": "x",\n "b": "y"\n}\n' assert runez.represented_json(data, indent=None) == '{"a": "x", "b": "y"}' assert runez.save_json(None, None, fatal=False) == 0 assert not runez.DRYRUN with runez.CaptureOutput(dryrun=True) as logged: assert runez.save_json(data, "sample.json") == 1 assert "Would save" in logged.pop() assert not runez.DRYRUN with runez.CaptureOutput() as logged: with pytest.raises(runez.system.AbortException) as exc: runez.read_json(None, fatal=True) assert "Can't read None" in str(exc) assert "Can't read None" in logged.pop() assert runez.read_json("sample.json") is None assert not logged assert runez.read_json("sample.json", default={}, logger=None) == {} assert not logged with monkeypatch.context() as m: m.setattr(runez.serialize, "open", runez.conftest.exception_raiser(), raising=False) assert runez.save_json(data, "sample.json", fatal=False) == -1 assert "Can't save" in logged.pop() assert runez.save_json(data, "sample.json", logger=logging.debug) == 1 assert "Saved " in logged.pop() with monkeypatch.context() as m: m.setattr(io, "open", runez.conftest.exception_raiser()) with pytest.raises(runez.system.AbortException) as exc: runez.read_json("sample.json", fatal=True, logger=None) assert "Can't read sample.json" in str(exc) assert runez.read_json("sample.json") is None assert not logged
def contents(self): """ :return dict: Deserialized contents of settings file """ if self._contents is None: self.set_contents( runez.read_json(self.path, default={}, fatal=False)) return self._contents
def from_file(cls, path): data = runez.read_json(path) if data: return cls( index=data.get("index"), install_info=TrackedInstallInfo.from_manifest_data(data), problem=data.get("problem"), source=data.get("source"), version=data.get("version"), )
def cleanup(self): """Cleanup older installs""" cutoff = time.time() - system.SETTINGS.install_timeout * 60 folder = system.SETTINGS.meta.full_path(self.name) removed_entry_points = runez.read_json(self.removed_entry_points_path, default=[], fatal=False) prefixes = {None: [], self.name: []} for name in self.entry_points: prefixes[name] = [] for name in removed_entry_points: prefixes[name] = [] if os.path.isdir(folder): for name in os.listdir(folder): if name.startswith("."): continue target = find_prefix(prefixes, name) if target in prefixes: fpath = os.path.join(folder, name) prefixes[target].append((os.path.getmtime(fpath), fpath)) # Sort each by last modified timestamp for target, cleanable in prefixes.items(): prefixes[target] = sorted(cleanable, reverse=True) rem_cleaned = 0 for target, cleanable in prefixes.items(): if not cleanable: if target in removed_entry_points: # No cleanable found for a removed entry-point -> count as cleaned rem_cleaned += 1 continue if target not in removed_entry_points: if cleanable[0][0] <= cutoff: # Latest is old enough now, cleanup all except latest cleanable = cleanable[1:] else: # Latest is too young, keep the last 2 cleanable = cleanable[2:] elif cleanable[0][0] <= cutoff: # Delete all removed entry points when old enough rem_cleaned += 1 else: # Removed entry point still too young, keep latest cleanable = cleanable[1:] for _, path in cleanable: runez.delete(path) if rem_cleaned >= len(removed_entry_points): runez.delete(self.removed_entry_points_path)
def from_file(cls, path): data = runez.read_json(path) if data: return cls( path, TrackedSettings.from_manifest_data(data), data.get("entrypoints"), install_info=TrackedInstallInfo.from_manifest_data(data), pinned=data.get("pinned"), version=data.get("version"), )
def test_to_dict(temp_folder): with runez.CaptureOutput() as logged: # Try with an object that isn't directly serializable, but has a to_dict() function data = {"a": "b"} obj = SomeRecord() obj.to_dict = lambda *_: data assert runez.save_json(obj, "sample2.json", logger=logging.debug) == 1 assert "Saved " in logged.pop() assert runez.read_json("sample2.json") == data assert not logged
def _add_config_file(self, path, base=None): path = runez.resolved_path(path, base=base) if path and all(c.source != path for c in self.configs) and os.path.exists(path): values = runez.read_json(path, logger=LOG.warning) if values: self.configs.append(RawConfig(self, path, values)) included = values.get("include") if included: for additional in runez.flattened(included): self._add_config_file(additional, base=os.path.dirname(path))
def expected_content(self, kind): path = self.expected_path(kind) content = self._expected.get(kind) if content is not None: return content content = runez.UNSET if os.path.exists(path): content = runez.read_json(path) self._expected[kind] = content return content
def entry_points(self): """ :return dict: Determined entry points from produced wheel, if available """ if self._entry_points is None: self._entry_points = runez.read_json(self.entry_points_path, fatal=None, default=None) if isinstance(self._entry_points, list): # For backwards compatibility with pickley <= v1.4.2 self._entry_points = dict((k, "") for k in self._entry_points) if self._entry_points is None: return {self.package_spec.dashed: ""} if runez.DRYRUN else {} return self._entry_points
def test_with_tty(monkeypatch, logged): with patch("runez.prompt.input", side_effect=mocked_input): monkeypatch.setattr(runez.SYS_INFO.terminal, "is_stdout_tty", True) expected = {"value": "foo"} with runez.TempFolder() as tmp: assert ask_once("test", "foo", base=tmp, serializer=custom_serializer) == expected assert runez.read_json("test.json", logger=None) == expected assert ask_once( "test", "bar", base=tmp) == expected # Ask a 2nd time, same response # Verify that if `serializer` returns None, value is not returned/stored with pytest.raises(Exception) as exc: ask_once("test-invalid", "invalid", base=tmp, serializer=custom_serializer, fatal=True, logger=None) assert "Invalid value provided for test-invalid" in str(exc) assert not os.path.exists("test-invalid.json") # Same, but don't raise exception (returns default) assert ask_once("test-invalid", "invalid", base=tmp, serializer=custom_serializer) is None assert not logged # Traced by default # Simulate no value provided with pytest.raises(Exception) as exc: ask_once("test-invalid", "", base=tmp, serializer=custom_serializer, fatal=True) assert "No value provided" in str(exc) assert "No value provided" in logged.pop() # Logged if fatal=True with patch("runez.prompt.input", side_effect=KeyboardInterrupt): # Simulate CTRL+C with pytest.raises(Exception) as exc: ask_once("test2", "test2", base=tmp, serializer=custom_serializer, fatal=True) assert "Cancelled by user" in str(exc)
def load(self): path = self._path old_path = None if path is None and self._suffix == "current": # pragma: no cover if self._package_spec.multi_named and not os.path.exists(self._path): # Temporary fix: pickley <v1.8 didn't standardize on dashed package name p = system.SETTINGS.meta.full_path(self._package_spec.pythonified, ".%s.json" % self._suffix) if os.path.exists(p): path = p old_path = self._path data = runez.read_json(path, default={}, fatal=False) self.set_from_dict(data, source=runez.short(path)) if old_path: # pragma: no cover self._path = old_path
def internal_install(self, force=False, verbose=True): """ :param bool force: If True, re-install even if package is already installed :param bool verbose: If True, show more extensive info """ with SoftLock(self.dist_folder, timeout=system.SETTINGS.install_timeout): self.refresh_desired(force=force) self.version = self.desired.version if not self.desired.valid: return runez.abort("Can't install %s: %s", self.name, self.desired.problem) if not force and self.current.equivalent(self.desired): system.inform( self.desired.representation(verbose=verbose, note="is already installed")) self.cleanup() return prev_entry_points = self.entry_points self.effective_install() new_entry_points = self.entry_points removed = set(prev_entry_points).difference(new_entry_points) if removed: old_removed = runez.read_json(self.removed_entry_points_path, default=[], fatal=False) removed = sorted(removed.union(old_removed)) runez.save_json(removed, self.removed_entry_points_path, fatal=False) # Delete wrapper/symlinks of removed entry points immediately for name in removed: runez.delete(system.SETTINGS.base.full_path(name)) self.cleanup() self.current.set_from(self.desired) self.current.save(fatal=False) msg = "Would install" if runez.DRYRUN else "Installed" system.inform("%s %s" % (msg, self.desired.representation(verbose=verbose)))
def test_json(temp_folder): assert runez.read_json(None, fatal=False) is None assert runez.save_json(None, None, fatal=False) == 0 data = {"a": "b"} assert not runez.DRYRUN with runez.CaptureOutput(dryrun=True) as logged: assert runez.save_json(data, "sample.json") == 1 assert "Would save" in logged.pop() assert not runez.DRYRUN with runez.CaptureOutput() as logged: assert runez.read_json("sample.json", fatal=False) is None assert "No file" in logged.pop() assert runez.read_json("sample.json", default={}, fatal=False) == {} assert not logged with patch("runez.serialize.open", side_effect=Exception): assert runez.save_json(data, "sample.json", fatal=False) == -1 assert "Couldn't save" in logged.pop() assert runez.save_json(data, "sample.json", logger=logging.debug) == 1 assert "Saved " in logged.pop() with patch("io.open", side_effect=Exception): assert runez.read_json("sample.json", fatal=False) is None assert "Couldn't read" in logged.pop() assert runez.read_json("sample.json", logger=logging.debug) == data assert "Read " in logged.pop() assert runez.read_json("sample.json", default=[], fatal=False) == [] assert "Wrong type" in logged.pop() with runez.CaptureOutput() as logged: # Try with an object that isn't directly serializable, but has a to_dict() function obj = SomeRecord() obj.to_dict = lambda *_: data assert runez.save_json(obj, "sample2.json", logger=logging.debug) == 1 assert "Saved " in logged.pop() assert runez.read_json("sample2.json", logger=logging.debug) == data assert "Read " in logged.pop()
def frozen(self): if self._frozen is None: self._frozen = runez.read_json(self.frozen_path, default={}) return self._frozen or {}
def test_install(cli): cli.expect_success("--dryrun --delivery wrap install tox", "Would wrap", "Would install tox") cli.expect_success("--dryrun --delivery symlink install tox", "Would symlink", "Would install tox") assert not os.path.exists(".pickley/audit.log") cli.expect_failure("check tox", "is not installed") cli.expect_failure("install six", "'six' is not a CLI") # Install tox, but add a few files + a bogus previous entry point to test cleanup runez.write(".pickley/tox/.entry-points.json", '["tox-old1", "tox-old2"]\n') runez.touch("tox-old1") runez.touch(".pickley/tox/tox-0.1/bin") runez.touch(".pickley/tox/tox-0.2/bin") runez.touch(".pickley/tox/tox-0.3/bin") runez.touch(".pickley/tox/tox-old1-0.1") runez.touch(".pickley/tox/tox-old1-0.2") cli.expect_success("--delivery wrap install tox", "Installed tox") # Old entry point removed immediately assert not os.path.exists("tox-old1") # Only 1 cleaned up immediately (latest + 1 kept) assert not os.path.exists(".pickley/tox/tox-0.1") assert not os.path.exists(".pickley/tox/tox-0.2") assert os.path.exists(".pickley/tox/tox-0.3") assert not os.path.exists(".pickley/tox/tox-old1-0.1") assert os.path.exists(".pickley/tox/tox-old1-0.2") assert runez.is_executable("tox") result = run_program("tox", "--version") assert "tox" in result.output cli.expect_success("auto-upgrade tox", "Skipping auto-upgrade") runez.delete(system.SETTINGS.meta.full_path("tox", ".ping")) cli.expect_success("auto-upgrade tox", "already installed") cli.expect_success("copy .pickley/tox tox-copy", "Copied") cli.expect_success("move tox-copy tox-relocated", "Moved") runez.delete("tox-relocated") # Verify that older versions and removed entry-points do get cleaned up runez.save_json({"install_timeout": 0}, "custom-timeout.json") cli.expect_success("-ccustom-timeout.json install tox", "already installed") # All cleaned up when enough time went by assert not os.path.exists(".pickley/tox/tox-0.3") assert not os.path.exists(".pickley/tox/tox-old1-0.2") cli.expect_success("check", "tox", "is installed") cli.expect_success( "check --verbose", "tox", "is installed (as %s wrap, channel: " % system.VENV_PACKAGER) # Simulate new version available latest = runez.read_json(".pickley/tox/.latest.json") latest["version"] = "10000.0" runez.save_json(latest, ".pickley/tox/.latest.json") cli.expect_failure("check", "tox", "can be upgraded to 10000.0") # Latest twine 2.0 requires py3 cli.expect_success("-ppex install twine==1.14.0", "Installed twine") cli.expect_success("list", "tox", "twine") cli.expect_success("list --verbose", "tox", "twine") assert find_uninstaller("tox") assert find_uninstaller("twine") cli.expect_success("uninstall twine", "Uninstalled twine") runez.write(".pickley/tox/.current.json", "") cli.expect_failure("check", "tox", "Couldn't read", "is not installed") cli.expect_success("uninstall --all", "Uninstalled tox", "entry points") assert not os.path.exists("tox") assert not os.path.exists(".pickley")