def test_clean_all_cleans_all_maps(mapped_doubler): mapped_doubler.map(range(1), tag='yes-me') mapped_doubler.map(range(1)) htmap.clean(all=True) assert len(htmap.get_tags()) == 0
def test_old_tag_not_in_tags(mapped_doubler): m = mapped_doubler.map(range(2), tag="old") m.wait(timeout=180) m.retag("new") assert "old" not in htmap.get_tags()
def test_old_tag_not_in_tags(mapped_doubler): m = mapped_doubler.map(range(2), tag='old') m.wait(timeout=180) m.retag('new') assert 'old' not in htmap.get_tags()
def test_clean_only_removes_transient_maps(mapped_doubler): mapped_doubler.map(range(1), tag='not-me') mapped_doubler.map(range(1)) htmap.clean() assert htmap.get_tags() == ('not-me', )
def test_clean_removes_all_transient_maps(mapped_doubler): mapped_doubler.map(range(1)) mapped_doubler.map(range(1)) mapped_doubler.map(range(1)) htmap.clean() assert len(htmap.get_tags()) == 0
def _get_tags(all: bool, pattern: List[str], tags: List[str]) -> Tuple[str, ...]: if all: tags = list(htmap.get_tags()) elif len(pattern) > 0: tags += _get_tags_from_patterns(pattern) _check_tags(tags) return tuple(tags)
def vacate(tags, all): """Force maps to give up their claimed resources.""" if all: tags = htmap.get_tags() _check_tags(tags) for tag in tags: with make_spinner(f'Vacating map {tag} ...') as spinner: _cli_load(tag).vacate() spinner.succeed(f'Vacated map {tag}')
def resume(tags, all): """Resume maps.""" if all: tags = htmap.get_tags() _check_tags(tags) for tag in tags: with make_spinner(f'Resuming map {tag} ...') as spinner: _cli_load(tag).resume() spinner.succeed(f'Resumed map {tag}')
def pause(tags, all): """Pause maps.""" if all: tags = htmap.get_tags() _check_tags(tags) for tag in tags: with make_spinner(f'Pausing map {tag} ...') as spinner: _cli_load(tag).pause() spinner.succeed(f'Paused map {tag}')
def hold(tags, all): """Hold maps.""" if all: tags = htmap.get_tags() _check_tags(tags) for tag in tags: with make_spinner(f'Holding map {tag} ...') as spinner: _cli_load(tag).hold() spinner.succeed(f'Held map {tag}')
def remove(tags, all, force): """Remove maps.""" if all: tags = htmap.get_tags() _check_tags(tags) for tag in tags: with make_spinner(f'Removing map {tag} ...') as spinner: _cli_load(tag).remove(force=force) spinner.succeed(f'Removed map {tag}')
def wait(tags, pattern, all): """Wait for maps to complete.""" if all: tags = htmap.get_tags() elif len(pattern) > 0: tags += _get_tags_from_patterns(pattern) _check_tags(tags) if len(tags) == 0: return maps = sorted((_cli_load(tag) for tag in tags), key=lambda m: (m.is_transient, m.tag)) with make_spinner(text='Reading map component statuses...'): read_events(maps) try: longest_tag_len = max(len(tag) for tag in tags) bar_width = min(shutil.get_terminal_size().columns, TOTAL_WIDTH - (longest_tag_len + 1)) click.echo('\n' * (len(maps) - 1)) while any(not map.is_done for map in maps): bars = [] for map in maps: sc = collections.Counter(map.component_statuses) bar_lens = { status: _calculate_bar_component_len(sc[status], len(map), bar_width) for status, _ in STATUS_AND_COLOR } bar_lens[htmap.ComponentStatus.IDLE] += bar_width - sum( bar_lens.values()) bar = ''.join([ click.style('█' * bar_lens[status], fg=color) for status, color in STATUS_AND_COLOR ]) bars.append(f'{map.tag.ljust(longest_tag_len)} {bar}') msg = '\n'.join(bars) move = f'\033[{len(maps)}A\r' sys.stdout.write(move) click.echo(msg) time.sleep(1) except KeyboardInterrupt: # bypass click's interrupt handling and let it exit quietly return
def map(tags, all): """Rerun all of the components of any number of maps.""" if all: tags = htmap.get_tags() _check_tags(tags) for tag in tags: m = _cli_load(tag) with make_spinner(f'Rerunning map {tag} ...') as spinner: m.rerun() spinner.succeed(f'Reran map {tag}')
def hold(tags, pattern, all): """Hold maps.""" if all: tags = htmap.get_tags() elif len(pattern) > 0: tags += _get_tags_from_patterns(pattern) _check_tags(tags) for tag in tags: with make_spinner(f'Holding map {tag} ...') as spinner: _cli_load(tag).hold() spinner.succeed(f'Held map {tag}')
def resume(tags, pattern, all): """Resume maps.""" if all: tags = htmap.get_tags() elif len(pattern) > 0: tags += _get_tags_from_patterns(pattern) _check_tags(tags) for tag in tags: with make_spinner(f'Resuming map {tag} ...') as spinner: _cli_load(tag).resume() spinner.succeed(f'Resumed map {tag}')
def errors(tags, all, limit): """Look at detailed error reports for a map.""" if all: tags = htmap.get_tags() _check_tags(tags) count = 0 for tag in tags: m = _cli_load(tag) for report in m.error_reports(): click.echo(report) count += 1 if 0 < limit <= count: return
def errors(tags, pattern, all, limit): """Look at detailed error reports for a map.""" if all: tags = htmap.get_tags() elif len(pattern) > 0: tags += _get_tags_from_patterns(pattern) _check_tags(tags) count = 0 for tag in tags: m = _cli_load(tag) for report in m.error_reports(): click.echo(report) count += 1 if 0 < limit <= count: return
def reasons(tags, all): """Print the hold reasons for maps.""" if all: tags = htmap.get_tags() _check_tags(tags) reps = [] for tag in tags: m = _cli_load(tag) if len(m.holds) == 0: continue name = click.style( f'Map {m.tag} ({len(m.holds)} hold{"s" if len(m.holds) > 1 else ""})', bold=True, ) reps.append(f'{name}\n{m.hold_report()}') click.echo('\n'.join(reps))
def _get_tags_from_patterns(patterns): return tuple(set(sum((htmap.get_tags(p) for p in patterns), ())))
def test_tags(mapped_doubler): mapped_doubler.map(range(1), tag='a') mapped_doubler.map(range(1), tag='b') mapped_doubler.map(range(1), tag='c') assert set(htmap.get_tags()) == {'a', 'b', 'c'}
def status(no_state, no_meta, format, live, no_color): """ Print the status of all maps. Transient maps are prefixed with a * """ if format != 'text' and live: click.echo('ERROR: cannot produce non-text live data.', err=True) sys.exit(1) maps = sorted((_cli_load(tag) for tag in htmap.get_tags()), key=lambda m: (m.is_transient, m.tag)) if not no_state: with make_spinner(text='Reading map component statuses...'): read_events(maps) shared_kwargs = dict( include_state=not no_state, include_meta=not no_meta, ) if format == 'json': msg = htmap.status_json(maps, **shared_kwargs) elif format == 'json_compact': msg = htmap.status_json(maps, **shared_kwargs, compact=True) elif format == 'csv': msg = htmap.status_csv(maps, **shared_kwargs) elif format == 'text': msg = _status( maps, **shared_kwargs, header_fmt=_HEADER_FMT if not no_color else None, row_fmt=_RowFmt(maps) if not no_color else None, ) else: click.echo(f'ERROR: unknown format option "{format}"', err=True) sys.exit(1) click.echo(msg) try: while live: prev_len_lines = [len(line) for line in msg.splitlines()] maps = sorted(htmap.load_maps(), key=lambda m: (m.is_transient, m.tag)) msg = _status( maps, **shared_kwargs, header_fmt=_HEADER_FMT if not no_color else None, row_fmt=_RowFmt(maps) if not no_color else None, # don't cache, must pass fresh each time ) move = f'\033[{len(prev_len_lines)}A\r' clear = '\n'.join(' ' * l for l in prev_len_lines) + '\n' sys.stdout.write(move + clear + move) click.echo(msg) time.sleep(1) except KeyboardInterrupt: # bypass click's interrupt handling and let it exit quietly return
def _autocomplete_tag(ctx, args, incomplete): return sorted(tag for tag in htmap.get_tags() if tag.startswith(incomplete) and tag not in args)
def status(state, meta, format, live, color): """ Print a status table for all of your maps. Transient maps are prefixed with a leading "*". """ if format != "text" and live: click.echo("ERROR: cannot produce non-text live data.", err=True) sys.exit(1) maps = sorted( (_cli_load(tag) for tag in htmap.get_tags()), key=lambda m: (m.is_transient, m.tag), ) for map in maps: if state: with make_spinner( text=f"Reading component statuses for map {map.tag}..."): map.component_statuses if meta: with make_spinner( text=f"Determining local data usage for map {map.tag}..."): map.local_data shared_kwargs = dict( include_state=state, include_meta=meta, ) if format == "json": msg = htmap.status_json(maps, **shared_kwargs) elif format == "json_compact": msg = htmap.status_json(maps, **shared_kwargs, compact=True) elif format == "csv": msg = htmap.status_csv(maps, **shared_kwargs) elif format == "text": msg = _status( maps, **shared_kwargs, header_fmt=_HEADER_FMT if color else None, row_fmt=_RowFmt(maps) if color else None, ) else: # pragma: unreachable # this is a safeguard; this code is actually unreachable, because # click detects the invalid choice before we hit this click.echo(f'ERROR: unknown format option "{format}"', err=True) sys.exit(2) click.echo(msg) try: while live: prev_lines = list(msg.splitlines()) prev_len_lines = [len(line) for line in prev_lines] maps = sorted(htmap.load_maps(), key=lambda m: (m.is_transient, m.tag)) msg = _status( maps, **shared_kwargs, header_fmt=_HEADER_FMT if color else None, row_fmt=_RowFmt(maps) if color else None, # don't cache, must pass fresh each time ) move = f"\033[{len(prev_len_lines)}A\r" clear = "\n".join(" " * len(click.unstyle(line)) for line in prev_lines) + "\n" sys.stdout.write(move + clear + move) click.echo(msg) time.sleep(1) except KeyboardInterrupt: # bypass click's interrupt handling and let it exit quietly return
def _fmt_tag_list(pattern: Optional[str] = None) -> str: return "\n".join(htmap.get_tags(pattern))
def _get_tags_from_patterns(patterns: List[str]) -> List[str]: return list(set(sum((htmap.get_tags(p) for p in patterns), ())))
def _autocomplete_tag(ctx, args, incomplete): return [tag for tag in htmap.get_tags() if incomplete in tag]
def test_get_tag_with_pattern(mapped_doubler, tags, pattern, expected): for tag in tags: mapped_doubler.map(range(1), tag = tag) assert set(htmap.get_tags(pattern)) == set(expected)
def test_get_tags(mapped_doubler): mapped_doubler.map(range(1), tag="a") mapped_doubler.map(range(1), tag="b") mapped_doubler.map(range(1), tag="c") assert set(htmap.get_tags()) == {"a", "b", "c"}
def _tag_list() -> str: return '\n'.join(htmap.get_tags())