Пример #1
0
class DetailedExecStopProgress(ExecStopProgress):
    def __init__(self, color: bool):
        super().__init__()
        self._color = color
        self._spinner = SPINNER
        self._printer = TTYPrinter()
        self._lineno = 0

    def tick(self, running: bool) -> None:
        new_time = self.time_factory()
        dt = new_time - self._time

        if running:
            msg = (style("-", fg="yellow") +
                   f"Wait for stopping {next(self._spinner)} [{dt:.1f} sec]")
        else:
            msg = style("√", fg="green") + " Stopped"

        self._printer.print(
            msg,
            lineno=self._lineno,
        )

    def timeout(self) -> None:
        secho()
        secho("× Warning !!!", fg="red")
        secho(
            "× The attached session was disconnected "
            "but the exec process is still alive.",
            fg="red",
        )
 def test_two_messages(self, capfd: Any, printer: TTYPrinter) -> None:
     printer.print("message1")
     printer.print("message2")
     printer.close()
     out, err = capfd.readouterr()
     assert err == ""
     assert out == f"message1{linesep}message2{linesep}"
Пример #3
0
class DetailedJobStopProgress(JobStopProgress):
    def __init__(self, color: bool):
        super().__init__()
        self._color = color
        self._spinner = SPINNER
        self._printer = TTYPrinter()
        self._lineno = 0

    def detach(self, job: JobDescription) -> None:
        secho()
        secho("× Terminal was detached but job is still running", fg="red")
        secho("Re-attach to job:", dim=True, fg="yellow")
        secho(f"  neuro attach {job.id}", dim=True)
        secho("Check job status:", dim=True, fg="yellow")
        secho(f"  neuro status {job.id}", dim=True)
        secho("Kill job:", dim=True, fg="yellow")
        secho(f"  neuro kill {job.id}", dim=True)
        secho("Fetch job logs:", dim=True, fg="yellow")
        secho(f"  neuro logs {job.id}", dim=True)

    def kill(self, job: JobDescription) -> None:
        secho()
        secho("× Job was killed", fg="red")
        secho("Get job status:", dim=True, fg="yellow")
        secho(f"  neuro status {job.id}", dim=True)
        secho("Fetch job logs:", dim=True, fg="yellow")
        secho(f"  neuro logs {job.id}", dim=True)

    def tick(self, job: JobDescription) -> None:
        new_time = self.time_factory()
        dt = new_time - self._time

        if job.status == JobStatus.RUNNING:
            msg = (style("-", fg="yellow") +
                   f" Wait for stop {next(self._spinner)} [{dt:.1f} sec]")
        else:
            msg = style("√", fg="green") + " Stopped"

        if not self._color:
            msg = unstyle(msg)
        self._printer.print(
            msg,
            lineno=self._lineno,
        )

    def timeout(self, job: JobDescription) -> None:
        secho()
        secho("× Warning !!!", fg="red")
        secho(
            "× The attached session was disconnected but the job is still alive.",
            fg="red",
        )
        secho("Reconnect to the job:", dim=True, fg="yellow")
        secho(f"  neuro attach {job.id}", dim=True)
        secho("Terminate the job:", dim=True, fg="yellow")
        secho(f"  neuro kill {job.id}", dim=True)
    def test_multiline(self, printer: TTYPrinter, capfd: Any) -> None:
        CSI = "\033["
        assert printer.total_lines == 0
        res = printer.print("message1\nmessage2")
        assert printer.total_lines == 2
        assert res == "message1\nmessage2\n"

        res = printer.print("message3\nmessage4", 1)
        assert printer.total_lines == 3
        assert res == f"{CSI}1Amessage3{CSI}0K\nmessage4\n"

        res = printer.print("message5\nmessage6", 10)
        assert printer.total_lines == 12
        assert res == f"\n\n\n\n\n\n\n{CSI}1Amessage5\nmessage6\n"

        res = printer.print("message7\nmessage8", 5)
        assert printer.total_lines == 12
        assert res == f"{CSI}7Amessage7{CSI}0K\nmessage8{CSI}0K\n{CSI}5B"
Пример #5
0
class DetailedJobStartProgress(JobStartProgress):
    def __init__(self, color: bool):
        self._time = time.time()
        self._color = color
        self._prev = ""
        if sys.platform == "win32":
            self._spinner = itertools.cycle("-\\|/")
        else:
            self._spinner = itertools.cycle("◢◣◤◥")
        self._printer = TTYPrinter()
        self._lineno = 0

    def __call__(self, job: JobDescription) -> None:
        new_time = time.time()
        dt = new_time - self._time
        msg = "Status: " + format_job_status(job.status)
        reason = self._get_status_reason_message(job)
        if reason:
            msg += " " + style(reason, bold=True)
        description = self._get_status_description_message(job)
        if description:
            msg += " " + description

        if not self._color:
            msg = unstyle(msg)
        if msg != self._prev:
            if self._prev:
                self._printer.print(self._prev, lineno=self._lineno)
            self._prev = msg
            self._lineno = self._printer.total_lines
            self._printer.print(msg)
        else:
            self._printer.print(f"{msg} {next(self._spinner)} [{dt:.1f} sec]",
                                lineno=self._lineno)
 def test_message_lineno(self, printer: TTYPrinter, capfd: Any) -> None:
     assert printer.total_lines == 0
     printer.print("message1")
     assert printer.total_lines == 1
     printer.print("message1-replace", 0)
     assert printer.total_lines == 1
     printer.print("message3", 2)
     assert printer.total_lines == 3
     printer.print("message7", 6)
     assert printer.total_lines == 7
     printer.print("message2", 1)
     assert printer.total_lines == 7
     printer.close()
     out, err = capfd.readouterr()
     assert err == ""
     assert "message1" in out
     assert "message1-replace" in out
     assert "message3" in out
     assert "message7" in out
     assert "message2" in out
     CSI = "\033["
     assert CSI in out
     assert f"{CSI}0A" not in out
     assert f"{CSI}0B" not in out
Пример #7
0
class TTYProgress(BaseStorageProgress):
    HEIGHT = 10

    def __init__(self, root: Root) -> None:
        self.painter = get_painter(root.color, quote=True)
        self.printer = TTYPrinter()
        self.half_width = (root.terminal_size[0] - 10) // 2
        self.full_width = root.terminal_size[0] - 20
        self.lines: List[Tuple[URL, bool, str]] = []
        self.cur_dir: Optional[URL] = None
        self.verbose = root.verbosity > 0

    def fmt_url(self, url: URL, type: FileStatusType, *, half: bool) -> str:
        label = str(url)
        if half:
            width = self.half_width
        else:
            width = self.full_width
        while len(label) > width:
            parts = list(url.parts)
            if len(parts) < 2:
                break
            if parts[0] == "/":
                if len(parts) < 3:
                    slash = "/"
                    break
                slash, first, second, *last = parts
                if first == "...":
                    if last:
                        parts = ["..."] + last
                    else:
                        break
                else:
                    parts = ["...", second] + last
            else:
                slash = ""
                # len(parts) > 1 always
                first, second, *last = parts
                if first == "...":
                    if last:
                        parts = ["..."] + last
                    else:
                        break
                else:
                    parts = ["...", second] + last
            if url.host or slash:
                pre = f"//{url.host or ''}{slash}"
            else:
                pre = ""
            url = URL(f"{url.scheme}:{pre}{'/'.join(parts)}")
            label = str(url)
        return self.fmt_str(label, type)

    def fmt_str(self, label: str, type: FileStatusType) -> str:
        return self.painter.paint(label, type)

    def fmt_size(self, size: int) -> str:
        return format_size(size)

    def begin(self, src: URL, dst: URL) -> None:
        if self.verbose:
            click.echo("Copy")
            click.echo(self.fmt_str(str(src), FileStatusType.DIRECTORY))
            click.echo("=>")
            click.echo(self.fmt_str(str(dst), FileStatusType.DIRECTORY))
        else:
            src_label = self.fmt_url(src, FileStatusType.DIRECTORY, half=True)
            dst_label = self.fmt_url(dst, FileStatusType.DIRECTORY, half=True)
            click.echo(f"Copy {src_label} => {dst_label}")

    def enter(self, data: StorageProgressEnterDir) -> None:
        self._enter_dir(data.src)

    def _enter_dir(self, src: URL) -> None:
        fmt_src = self.fmt_url(src, FileStatusType.DIRECTORY, half=False)
        self.append(src, f"{fmt_src} ...", is_dir=True)
        self.cur_dir = src

    def leave(self, data: StorageProgressLeaveDir) -> None:
        src = self.fmt_url(data.src, FileStatusType.DIRECTORY, half=False)
        if self.lines and self.lines[-1][0] == data.src:
            self.lines.pop()
        self.append(data.src, f"{src} DONE", is_dir=True)
        self.cur_dir = None

    def start(self, data: StorageProgressStart) -> None:
        src = self.fmt_str(data.src.name, FileStatusType.FILE)
        progress = 0
        current = self.fmt_size(0)
        total = self.fmt_size(data.size)
        self._append_file(data.src,
                          f"{src} [{progress:.2f}%] {current} of {total}")

    def complete(self, data: StorageProgressComplete) -> None:
        src = self.fmt_str(data.src.name, FileStatusType.FILE)
        total = self.fmt_size(data.size)
        self.replace(data.src, f"{src} {total}")

    def step(self, data: StorageProgressStep) -> None:
        src = self.fmt_str(data.src.name, FileStatusType.FILE)
        progress = (100 * data.current) / data.size
        current = self.fmt_size(data.current)
        total = self.fmt_size(data.size)
        self.replace(data.src, f"{src} [{progress:.2f}%] {current} of {total}")

    def fail(self, data: StorageProgressFail) -> None:
        src = self.fmt_str(str(data.src), FileStatusType.FILE)
        dst = self.fmt_str(str(data.dst), FileStatusType.FILE)
        click.echo(
            click.style("Failure:", fg="red") +
            f" {src} -> {dst} [{data.message}]",
            err=True,
        )
        # clear lines to sync with writing to stderr
        self.lines = []

    def _append_file(self, key: URL, msg: str) -> None:
        parent = key.parent
        if self.cur_dir is not None and self.cur_dir != parent:
            self._enter_dir(parent)
        self.append(key, msg)

    def append(self, key: URL, msg: str, is_dir: bool = False) -> None:
        self.lines.append((key, is_dir, msg))
        if len(self.lines) > self.HEIGHT:
            if not self.lines[0][1]:
                # top line is not a dir, drop it.
                del self.lines[0]
            elif self.lines[1][1]:
                # second line is a dir, drop the first line.
                del self.lines[0]
            else:
                # there is a file line under a dir line, drop the file line.
                del self.lines[1]
        for lineno, line in enumerate(self.lines):
            self.printer.print(line[2], lineno)

    def replace(self, key: URL, msg: str) -> None:
        for i in range(len(self.lines))[::-1]:
            line = self.lines[i]
            if line[0] == key:
                self.lines[i] = (key, False, msg)
                self.printer.print(self.lines[i][2], i)
                break
        else:
            self._append_file(key, msg)
Пример #8
0
class DetailedDockerImageProgress(DockerImageProgress):
    def __init__(self) -> None:
        self._mapping: Dict[str, int] = {}
        self._printer = TTYPrinter()

    def push(self, data: ImageProgressPush) -> None:
        src = click.style(str(data.src), bold=True)
        dst = click.style(str(data.dst), bold=True)
        self._printer.print(f"Pushing image {src} => {dst}")

    def pull(self, data: ImageProgressPull) -> None:
        src = click.style(str(data.src), bold=True)
        dst = click.style(str(data.dst), bold=True)
        self._printer.print(f"Pulling image {src} => {dst}")

    def step(self, data: ImageProgressStep) -> None:
        if data.layer_id:
            if data.layer_id in self._mapping.keys():
                lineno = self._mapping[data.layer_id]
                self._printer.print(data.message, lineno)
            else:
                self._mapping[data.layer_id] = self._printer.total_lines
                self._printer.print(data.message)
        else:
            self._printer.print(data.message)

    def save(self, data: ImageProgressSave) -> None:
        job = click.style(str(data.job), bold=True)
        dst = click.style(str(data.dst), bold=True)
        self._printer.print(f"Saving {job} -> {dst}")

    def commit_started(self, data: ImageCommitStarted) -> None:
        img = click.style(str(data.target_image), bold=True)
        self._printer.print(
            f"Creating image {img} image from the job container")

    def commit_finished(self, data: ImageCommitFinished) -> None:
        self._printer.print("Image created")

    def close(self) -> None:
        self._printer.close()
Пример #9
0
class DetailedJobStartProgress(JobStartProgress):
    def __init__(self, color: bool):
        self._time = self.time_factory()
        self._color = color
        self._prev = ""
        self._spinner = SPINNER
        self._printer = TTYPrinter()
        self._lineno = 0

    def begin(self, job: JobDescription) -> None:
        self._printer.print(
            style("√ ", fg="green") + style("Job ID", bold=True) +
            f": {job.id} ")
        if job.name:
            self._printer.print(
                style("√ ", fg="green") + style("Name", bold=True) +
                f": {job.name}")

    def step(self, job: JobDescription) -> None:
        new_time = self.time_factory()
        dt = new_time - self._time
        msg = "Status: " + format_job_status(job.status)
        reason = self._get_status_reason_message(job)
        if reason:
            msg += " " + style(reason, bold=True)
        description = self._get_status_description_message(job)
        if description:
            msg += " " + description

        if job.status == JobStatus.PENDING:
            msg = style("- ", fg="yellow") + msg
        elif job.status == JobStatus.FAILED:
            msg = style("× ", fg="red") + msg
        else:
            # RUNNING or SUCCEDED
            msg = style("√ ", fg="green") + msg

        if not self._color:
            msg = unstyle(msg)
        if msg != self._prev:
            if self._prev:
                self._printer.print(self._prev, lineno=self._lineno)
            self._prev = msg
            self._lineno = self._printer.total_lines
            self._printer.print(msg)
        else:
            self._printer.print(f"{msg} {next(self._spinner)} [{dt:.1f} sec]",
                                lineno=self._lineno)

    def end(self, job: JobDescription) -> None:
        out = []

        if job.status != JobStatus.FAILED:
            http_url = job.http_url
            if http_url:
                out.append(
                    style("√ ", fg="green") + style("Http URL", bold=True) +
                    f": {http_url}")
            if job.life_span:
                limit = humanize.naturaldelta(
                    datetime.timedelta(seconds=job.life_span))
                out.append(
                    style("√ ", fg="green") + style(
                        f"The job will die in {limit}. ",
                        fg="yellow",
                    ) + "See --life-span option documentation for details.", )
            self._printer.print("\n".join(out))
Пример #10
0
 def test_one_message(self, capfd: Any, printer: TTYPrinter) -> None:
     printer.print("message")
     printer.close()
     out, err = capfd.readouterr()
     assert err == ""
     assert out == f"message{linesep}"