def __init__(self, color: bool): self._time = self.time_factory() self._color = color self._prev = "" self._spinner = SPINNER self._printer = TTYPrinter() self._lineno = 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}"
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
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 __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 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"
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)
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))
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)
def __init__(self) -> None: self._mapping: Dict[str, int] = {} self._printer = TTYPrinter()
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()
def __init__(self, color: bool): super().__init__() self._color = color self._spinner = SPINNER self._printer = TTYPrinter() self._lineno = 0
def printer(self, click_tty_emulation: Any) -> TTYPrinter: return TTYPrinter()
def test_no_messages(self, capfd: Any, printer: TTYPrinter) -> None: printer.close() out, err = capfd.readouterr() assert err == "" assert out == ""
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}"
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