Skip to content

API Reference

Robust API reference for the console package.

Printer

Color-aware, nest-friendly pretty printer for Python structures.

Features

  • Dicts: inline scalars (key: value), nested values expand with indentation.
  • Lists/Tuples/Sets: index/marker labels, nested values expand.
  • Optional ANSI color (auto disabled when the stream is not a TTY).
  • Optional type hints ((str), (int), ...).
  • String truncation for long values.

Notes

The color palette is loaded from ansi_colors.json which is ensured on first use via :func:sciwork.console.create_ansi_colors_config. The default location is <cwd>/sciwork/configs/ansi_colors.json; switch to the user location by calling :class:Printer with prefer='user'.

Source code in src/sciwork/console/printer.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
		self,
		*,
		prefer: Literal['project', 'user'] = "project",
		use_color: bool = True,
		indent_unit: int = 4
) -> None:
	self._indent_unit = int(indent_unit)
	self._use_color_flag = bool(use_color)
	self._ctx_stack: list[dict[str, object]] = []

	theme_path = ensure_ansi_colors(prefer=prefer)
	palette_theme = RobustConfig().load_json_config(theme_path).to_dict()
	self.palette = self._load_palette(palette_theme)

__enter__()

Enter context.

Saves actual "user-adjustable" states so that they are renewed by exit. Attributes are changeable within the block: with printer as p: p._use_color_flag = False p.printer(obj)

Source code in src/sciwork/console/printer.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
	def __enter__(self):
		"""
        Enter context.

        Saves actual "user-adjustable" states so that they are renewed by __exit__.
        Attributes are changeable within the block:
            with printer as p:
                p._use_color_flag = False
                p.printer(obj)
        """
		self._ctx_stack.append({
			"use_color": getattr(self, "_use_color_flag", True),
			"palette": getattr(self, "palette", {}),
		})
		return self

__exit__(exc_type, exc_val, exc_tb)

Exit context: renews the state before entering the block.

Returns:

Type Description
bool

False.

Source code in src/sciwork/console/printer.py
84
85
86
87
88
89
90
91
92
93
94
95
96
	def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
		"""
        Exit context: renews the state before entering the block.
        :return: False.
        """
		state = self._ctx_stack.pop() if self._ctx_stack else None
		if state:
			try:
				self._use_color_flag = state["use_color"]  # type: ignore[assignment]
				self.palette = state["palette"]  # type: ignore[assignment]
			except Exception:
				pass
		return False

__repr__()

Unambiguous representation of printer.

Source code in src/sciwork/console/printer.py
57
58
59
60
61
def __repr__(self) -> str:
	"""Unambiguous representation of printer."""
	return (f"{self.__class__.__name__}("
	        f"use_color={self._use_color_flag}, "
	        f"palette_keys={list(self.palette.keys())}")

__str__()

Concise representation of printer.

Source code in src/sciwork/console/printer.py
63
64
65
66
def __str__(self) -> str:
	"""Concise representation of printer."""
	state = "on" if self._use_color_flag else "off"
	return f"{self.__class__.__name__} (color={state})"

printer(obj, indent=0, sort_keys=True, show_types=False, max_str=200, stream=sys.stdout)

Pretty-prints nested Python structures (dict, list, tuple, set, etc.) with inline keys/indices on the same line. Supports optional color, type hints, sorting of dict keys, and string truncation for readability.

Behavior

  • Dicts: 'key: value' on a single line when value is scalar; nested values expand with indentation.
  • Lists: '[idx]: value' on a single line when scalar; nested values expand.
  • Tuples: '(idx): value' labels.
  • Sets: '{•}: value' labels (order not guaranteed; sorted when possible).
  • Scalars: printed directly; long strings are truncated to 'max_str' characters.

Parameters:

Name Type Description Default
obj Any

Object to be printed.

required
indent int

Current indentation level (internal for recursion).

0
sort_keys bool

Sort dictionary keys alphabetically (safe if keys are comparable).

True
show_types bool

Append the value's type in a dim style, e.g., '(str)'.

False
max_str Optional[int]

Truncate strings longer than this length with an ellipsis.

200
stream _Writable

Output stream; defaults to sys.stdout. Notes ----- * Colors automatically fall back to plain text if ANSI is not supported. * Errors are logged using 'logging.error(..., exc_info=True)'.

stdout
Source code in src/sciwork/console/printer.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
	def printer(
			self,
			obj: Any,
			indent: int = 0,
			sort_keys: bool = True,
			show_types: bool = False,
			max_str: Optional[int] = 200,
			stream: _Writable = sys.stdout
	) -> None:
		"""
        Pretty-prints nested Python structures (dict, list, tuple, set, etc.) with inline
        keys/indices on the same line. Supports optional color, type hints, sorting of dict keys,
        and string truncation for readability.

        Behavior
        --------
        * Dicts: 'key: value' on a single line when value is scalar; nested values expand with indentation.
        * Lists: '[idx]: value' on a single line when scalar; nested values expand.
        * Tuples: '(idx): value' labels.
        * Sets: '{•}: value' labels (order not guaranteed; sorted when possible).
        * Scalars: printed directly; long strings are truncated to 'max_str' characters.

        :param obj: Object to be printed.
        :param indent: Current indentation level (internal for recursion).
        :param sort_keys: Sort dictionary keys alphabetically (safe if keys are comparable).
        :param show_types: Append the value's type in a dim style, e.g., '(str)'.
        :param max_str: Truncate strings longer than this length with an ellipsis.
        :param stream: Output stream; defaults to sys.stdout.

        Notes
        -----
        * Colors automatically fall back to plain text if ANSI is not supported.
        * Errors are logged using 'logging.error(..., exc_info=True)'.
        """
		enable_color = self._supports_color(stream, self._use_color_flag)
		orig_palette = self.palette
		try:
			if not enable_color:
				self.palette = {k: "" for k in orig_palette}

			# Mapping
			if isinstance(obj, _Mapping):
				self._print_mapping(
					obj, indent=indent, sort_keys=sort_keys, show_types=show_types,
					max_str=max_str, stream=stream
				)
				return

			# Iterable
			if isinstance(obj, _Iterable) and not isinstance(obj, (str, bytes, dict)):
				self._print_iterable(
					obj, indent=indent, show_types=show_types,
					max_str=max_str, stream=stream
				)
				return

			# Scalar
			self._print_scalar(
				obj, pad=self._pad(indent), max_str=max_str, show_types=show_types,
				stream=stream
			)

		except Exception as e:
			LOG.error(f"Error while printing object: {e}", exc_info=True)
			raise
		finally:
			self.palette = orig_palette

options: members: true show_root_heading: true heading_level: 2

Console

Bases: Prompter

High-level console helper that extends :class:Printer with convenience methods for timing, rules (horizontal lines), and a simple dot-loading animation.

Color Usage

The class reuses the :class:Printer palette and will look up optional roles:

-"rule": color for horizontal rules - "dots": color for the dot animation

If a role is not present in the palette, it falls back to a neutral style.

Source code in src/sciwork/console/prompter.py
40
41
42
43
44
def __init__(self, *, use_color: bool = True, stream: _Writable = sys.stdout, **kwargs) -> None:
	super().__init__(use_color=use_color, **kwargs)
	self.stream: _Writable = stream
	self._colors_enabled: bool = self._supports_color(self.stream, self._use_color_flag)
	self._ensure_prompt_palette()

dot_loading_animation(current_count, *, max_dots=5, stream=sys.stdout, color_role='dots', left='', right='')

Print one step of a dot-loading animation and return the updated count. Dots are colorized using a palette role (if color is supported).

Parameters:

Name Type Description Default
current_count int

Current number of dots already printed in the active cycle.

required
max_dots int

Maximum dots before resetting. Defaults to 5.

5
stream TextIO

Output stream; defaults to sys.stdout.

stdout
color_role str

Palette role to colorize the dots. Defaults to "dots".

'dots'
left str

Static text printed to the left of the dots.

''
right str

Static text printed to the right of the dots.

''

Returns:

Type Description
int

Updated dot counter to be fed back into the next call.

Source code in src/sciwork/console/console.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def dot_loading_animation(
		self,
		current_count: int,
		*,
		max_dots: int = 5,
		stream: TextIO = sys.stdout,
		color_role: str = "dots",
		left: str = "",
		right: str = ""
) -> int:
	"""
	Print one step of a dot-loading animation and return the updated count.
	Dots are colorized using a palette role (if color is supported).

	:param current_count: Current number of dots already printed in the active cycle.
	:param max_dots: Maximum dots before resetting. Defaults to 5.
	:param stream: Output stream; defaults to ``sys.stdout``.
	:param color_role: Palette role to colorize the dots. Defaults to ``"dots"``.
	:param left: Static text printed to the left of the dots.
	:param right: Static text printed to the right of the dots.
	:return: Updated dot counter to be fed back into the next call.
	"""
	use_color = self._supports_color(stream, self._use_color_flag)
	color = self.palette.get(color_role, "") if use_color else ""
	reset = self.palette.get("reset", "") if use_color else ""

	prefix = f"{color}{left}"
	suffix = f"{right}{reset}"

	return _next_dots_plain(
		current_count=current_count,
		max_dots=max_dots,
		stream=stream,
		prefix=prefix,
		suffix=suffix
	)

rule(times=1, *, prespace=True, char='-', width=None, stream=sys.stdout, color_role='rule')

Print a colored horizontal rule (optionally multiple times).

Parameters:

Name Type Description Default
times int

How many lines to print. Defaults to 1.

1
prespace bool

Print a blank line before the rules. Defaults to True.

True
char str

Single character used to build the rule. Defaults to "-".

'-'
width Optional[int]

Target width. If omitted, the terminal width is detected (fallback 100).

None
stream TextIO

Output stream; defaults to sys.stdout.

stdout
color_role str

Palette role to colorize the rule. Defaults to "rule".

'rule'

Returns:

Type Description
int

The width that was used for the rule.

Source code in src/sciwork/console/console.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def rule(
		self,
		times: int = 1,
		*,
		prespace: bool = True,
		char: str = "-",
		width: Optional[int] = None,
		stream: TextIO = sys.stdout,
		color_role: str = "rule"
) -> int:
	"""
	Print a colored horizontal rule (optionally multiple times).

	:param times: How many lines to print. Defaults to 1.
	:param prespace: Print a blank line before the rules. Defaults to ``True``.
	:param char: Single character used to build the rule. Defaults to ``"-"``.
	:param width: Target width. If omitted, the terminal width is detected (fallback 100).
	:param stream: Output stream; defaults to ``sys.stdout``.
	:param color_role: Palette role to colorize the rule. Defaults to ``"rule"``.
	:return: The width that was used for the rule.
	"""
	use_color = self._supports_color(stream, self._use_color_flag)
	color = self.palette.get(color_role, "") if use_color else ""
	reset = self.palette.get("reset", "") if use_color else ""
	return _print_rule_plain(
		times=times,
		prespace=prespace,
		char=char,
		width=width,
		stream=stream,
		prefix=color,
		suffix=reset
	)

time_count(start_time, end_time, *, ms=False) staticmethod

Format the elapsed time between two timestamps into a readable string.

Parameters:

Name Type Description Default
start_time float

Start time in seconds (e.g., from time.perf_counter()).

required
end_time float

End time in seconds.

required
ms bool

When True, append milliseconds as well.

False

Returns:

Type Description
str

Human-friendly time span, e.g. "1 h 02 min 03 s" or "45 s 120 ms" when ms=True.

Source code in src/sciwork/console/console.py
27
28
29
30
31
32
33
34
35
36
37
38
@staticmethod
def time_count(start_time: float, end_time: float, *, ms: bool = False) -> str:
	"""
	Format the elapsed time between two timestamps into a readable string.

	:param start_time: Start time in seconds (e.g., from ``time.perf_counter()``).
	:param end_time: End time in seconds.
	:param ms: When ``True``, append milliseconds as well.
	:return: Human-friendly time span, e.g. ``"1 h 02 min 03 s"`` or
			``"45 s 120 ms"`` when ``ms=True``.
	"""
	return format_interval(start_time, end_time, ms=ms)

options: members: true show_root_heading: true heading_level: 2

Prompter

Bases: Printer

Mixin that adds colored prompting utilities to console classes.

This mixin expects the subclass to expose:

  • self.palette: dict[str, str] with ANSI escapes.
  • self._use_color_flag: bool toggle for color usage.
  • self._supports_color(stream: TextIO, use_color_flag: bool) -> bool

Typically, :class:~sciwork.console.printer.Printer already provides these.

Source code in src/sciwork/console/prompter.py
40
41
42
43
44
def __init__(self, *, use_color: bool = True, stream: _Writable = sys.stdout, **kwargs) -> None:
	super().__init__(use_color=use_color, **kwargs)
	self.stream: _Writable = stream
	self._colors_enabled: bool = self._supports_color(self.stream, self._use_color_flag)
	self._ensure_prompt_palette()

confirm(message, *, default=False, stream=sys.stdout, input_func=input, yes_values=('y', 'yes'), no_values=('n', 'no'), transform=lambda s: s.strip().lower())

Ask a yes/no question with a colored prompt and return a boolean.

Parameters:

Name Type Description Default
message str

Question to show (e.g., "Proceed with deletion").

required
default bool

Default answer when the user presses Enter. Controls the [Y/n] or [y/N] hint.

False
stream _Stream

Output stream (colors are enabled only if TTY / color-capable).

stdout
input_func Callable[[str], str]

Input function to call (injectable for test).

input
yes_values Sequence[str]

Accepted case-insensitive strings for "yes".

('y', 'yes')
no_values Sequence[str]

Accepted case-insensitive strings for "no".

('n', 'no')
transform Callable[[str], str]

Normalization function applied to raw input (default: lowercase and strip).

lambda s: lower()

Returns:

Type Description
bool

True for yes, False for no.

Source code in src/sciwork/console/prompter.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def confirm(
		self,
		message: str,
		*,
		default: bool = False,
		stream: _Stream = sys.stdout,
		input_func: Callable[[str], str] = input,
		yes_values: Sequence[str] = ("y", "yes"),
		no_values: Sequence[str] = ("n", "no"),
		transform: Callable[[str], str] = lambda s: s.strip().lower()
) -> bool:
	"""
	Ask a yes/no question with a colored prompt and return a boolean.

	:param message: Question to show (e.g., ``"Proceed with deletion"``).
	:param default: Default answer when the user presses Enter.
					Controls the ``[Y/n]`` or ``[y/N]`` hint.
	:param stream: Output stream (colors are enabled only if TTY / color-capable).
	:param input_func: Input function to call (injectable for test).
	:param yes_values: Accepted case-insensitive strings for "yes".
	:param no_values: Accepted case-insensitive strings for "no".
	:param transform: Normalization function applied to raw input (default: lowercase and strip).
	:return: ``True`` for yes, ``False`` for no.
	"""
	allowed = tuple(x.lower() for x in (*yes_values, *no_values))
	y0 = next(iter(yes_values), "Y")
	n0 = next(iter(no_values), "N")
	yn_hint = "/".join([y0.upper() if default else y0, n0 if default else n0.upper()])

	ans = self.prompt(
		f"{message} {yn_hint}",
		default=("y" if default else "n"),
		choices=allowed,
		transform=transform,
		stream=stream,
		input_func=input_func
	)
	return ans in {v.lower() for v in yes_values}

print_lines(lines, *, stream=sys.stdout)

Print a block of lines to the console, flushing after each line. Safer on Windows than passing a giant multi-line string into input().

Parameters:

Name Type Description Default
lines Iterable[str]

Iterable of strings to print.

required
stream _Writable

Output stream; defaults to sys.stdout.

stdout
Source code in src/sciwork/console/prompter.py
56
57
58
59
60
61
62
63
64
65
66
def print_lines(self, lines: Iterable[str], *, stream: _Writable = sys.stdout) -> None:
	"""
	Print a block of lines to the console, flushing after each line.
	Safer on Windows than passing a giant multi-line string into input().

	:param lines: Iterable of strings to print.
	:param stream: Output stream; defaults to sys.stdout.
	"""
	stream = stream or self.stream
	for line in lines:
		print(line, file=stream)

prompt(message, *, default=None, choices=None, validate=None, transform=None, allow_empty=False, retries=3, stream=sys.stdout, input_func=input)

Prompt the user for a line of input with optional validation and coloring.

The prompt can display a default value and a list of choices. It the user submits an empty line and default is provided, the default is returned. A validate callback may raise to reject the input; a transform callback may convert text (e.g. str.strip or str.lower).

Parameters:

Name Type Description Default
message str

Prompt message shown to the user (without trailing colon).

required
default Optional[str]

Optional default value returned when the user presses Enter on an empty line.

None
choices Optional[Iterable[_T]]

Optional finite set of allowed values. If provided, input must be one of these.

None
validate Optional[Callable[[_T], None]]

Optional callable validate(value) that should raise on invalid input.

None
transform Optional[Callable[[str], _T]]

Optional callable transform(value) -> value to normalize the input.

None
allow_empty bool

Allow returning an empty string when no default is set. Defaults to False.

False
retries int

Number of attempts before raising ValueError. Defaults to 3.

3
stream _Stream

Output stream (colors are enabled only if TTY / color-capable).

stdout
input_func Callable[[str], str]

Input function to call (injectable for test).

input

Returns:

Type Description
Union[str, _T]

Final (validated and transformed) value.

Raises:

Type Description
ValueError

If the user exceeds the allowed number of attempts or input is invalid.

Source code in src/sciwork/console/prompter.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def prompt(
		self,
		message: str,
		*,
		default: Optional[str] = None,
		choices: Optional[Iterable[_T]] = None,
		validate: Optional[Callable[[_T], None]] = None,
		transform: Optional[Callable[[str], _T]] = None,
		allow_empty: bool = False,
		retries: int = 3,
		stream: _Stream = sys.stdout,
		input_func: Callable[[str], str] = input
) -> Union[str, _T]:
	"""
	Prompt the user for a line of input with optional validation and coloring.

	The prompt can display a *default* value and a list of *choices*. It the user
	submits an empty line and *default* is provided, the default is returned.
	A *validate* callback may raise to reject the input; a *transform* callback
	may convert text (e.g. ``str.strip`` or ``str.lower``).

	:param message: Prompt message shown to the user (without trailing colon).
	:param default: Optional default value returned when the user presses Enter on an empty line.
	:param choices: Optional finite set of allowed values. If provided, input must be one of these.
	:param validate: Optional callable ``validate(value)`` that should raise on invalid input.
	:param transform: Optional callable ``transform(value) -> value`` to normalize the input.
	:param allow_empty: Allow returning an empty string when no default is set. Defaults to ``False``.
	:param retries: Number of attempts before raising ``ValueError``. Defaults to ``3``.
	:param stream: Output stream (colors are enabled only if TTY / color-capable).
	:param input_func: Input function to call (injectable for test).
	:return: Final (validated and transformed) value.
	:raises ValueError: If the user exceeds the allowed number of attempts or input is invalid.
	"""
	prompt_c = self.palette.get("prompt", "")
	hint_c = self.palette.get("hint", "")
	err_c = self.palette.get("error", "")
	reset = self.palette.get("reset", "")

	hint_suffix = self._build_hint_suffix(default=default, choices=choices, hint_color=hint_c, reset=reset)
	base = f"{prompt_c}{message}{reset}"
	full_prompt = (f"{base} {hint_suffix}" if hint_suffix else base) + ": "

	attempts = self._normalize_retries(retries)
	for _ in range(attempts):
		raw = self._readline(input_func, full_prompt)

		if raw == "":
			if default is not None:
				return default
			if allow_empty:
				return ""
			print(f"{err_c}Input cannot be empty.{reset}", file=stream, flush=True)
			continue

		try:
			value: Any = self._apply_transform(raw, transform)
			self._check_choices(value, choices)
			self._run_validator(value, validate)
			return value
		except Exception as exc:
			print(f"{err_c}{exc}{reset}", file=stream, flush=True)
	raise ValueError("Too many invalid attempts.")

options: members: true show_root_heading: true heading_level: 2

Printer

Color-aware, nest-friendly pretty printer for Python structures.

Features

  • Dicts: inline scalars (key: value), nested values expand with indentation.
  • Lists/Tuples/Sets: index/marker labels, nested values expand.
  • Optional ANSI color (auto disabled when the stream is not a TTY).
  • Optional type hints ((str), (int), ...).
  • String truncation for long values.

Notes

The color palette is loaded from ansi_colors.json which is ensured on first use via :func:sciwork.console.create_ansi_colors_config. The default location is <cwd>/sciwork/configs/ansi_colors.json; switch to the user location by calling :class:Printer with prefer='user'.

Source code in src/sciwork/console/printer.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
		self,
		*,
		prefer: Literal['project', 'user'] = "project",
		use_color: bool = True,
		indent_unit: int = 4
) -> None:
	self._indent_unit = int(indent_unit)
	self._use_color_flag = bool(use_color)
	self._ctx_stack: list[dict[str, object]] = []

	theme_path = ensure_ansi_colors(prefer=prefer)
	palette_theme = RobustConfig().load_json_config(theme_path).to_dict()
	self.palette = self._load_palette(palette_theme)

__enter__()

Enter context.

Saves actual "user-adjustable" states so that they are renewed by exit. Attributes are changeable within the block: with printer as p: p._use_color_flag = False p.printer(obj)

Source code in src/sciwork/console/printer.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
	def __enter__(self):
		"""
        Enter context.

        Saves actual "user-adjustable" states so that they are renewed by __exit__.
        Attributes are changeable within the block:
            with printer as p:
                p._use_color_flag = False
                p.printer(obj)
        """
		self._ctx_stack.append({
			"use_color": getattr(self, "_use_color_flag", True),
			"palette": getattr(self, "palette", {}),
		})
		return self

__exit__(exc_type, exc_val, exc_tb)

Exit context: renews the state before entering the block.

Returns:

Type Description
bool

False.

Source code in src/sciwork/console/printer.py
84
85
86
87
88
89
90
91
92
93
94
95
96
	def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
		"""
        Exit context: renews the state before entering the block.
        :return: False.
        """
		state = self._ctx_stack.pop() if self._ctx_stack else None
		if state:
			try:
				self._use_color_flag = state["use_color"]  # type: ignore[assignment]
				self.palette = state["palette"]  # type: ignore[assignment]
			except Exception:
				pass
		return False

__repr__()

Unambiguous representation of printer.

Source code in src/sciwork/console/printer.py
57
58
59
60
61
def __repr__(self) -> str:
	"""Unambiguous representation of printer."""
	return (f"{self.__class__.__name__}("
	        f"use_color={self._use_color_flag}, "
	        f"palette_keys={list(self.palette.keys())}")

__str__()

Concise representation of printer.

Source code in src/sciwork/console/printer.py
63
64
65
66
def __str__(self) -> str:
	"""Concise representation of printer."""
	state = "on" if self._use_color_flag else "off"
	return f"{self.__class__.__name__} (color={state})"

printer(obj, indent=0, sort_keys=True, show_types=False, max_str=200, stream=sys.stdout)

Pretty-prints nested Python structures (dict, list, tuple, set, etc.) with inline keys/indices on the same line. Supports optional color, type hints, sorting of dict keys, and string truncation for readability.

Behavior

  • Dicts: 'key: value' on a single line when value is scalar; nested values expand with indentation.
  • Lists: '[idx]: value' on a single line when scalar; nested values expand.
  • Tuples: '(idx): value' labels.
  • Sets: '{•}: value' labels (order not guaranteed; sorted when possible).
  • Scalars: printed directly; long strings are truncated to 'max_str' characters.

Parameters:

Name Type Description Default
obj Any

Object to be printed.

required
indent int

Current indentation level (internal for recursion).

0
sort_keys bool

Sort dictionary keys alphabetically (safe if keys are comparable).

True
show_types bool

Append the value's type in a dim style, e.g., '(str)'.

False
max_str Optional[int]

Truncate strings longer than this length with an ellipsis.

200
stream _Writable

Output stream; defaults to sys.stdout. Notes ----- * Colors automatically fall back to plain text if ANSI is not supported. * Errors are logged using 'logging.error(..., exc_info=True)'.

stdout
Source code in src/sciwork/console/printer.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
	def printer(
			self,
			obj: Any,
			indent: int = 0,
			sort_keys: bool = True,
			show_types: bool = False,
			max_str: Optional[int] = 200,
			stream: _Writable = sys.stdout
	) -> None:
		"""
        Pretty-prints nested Python structures (dict, list, tuple, set, etc.) with inline
        keys/indices on the same line. Supports optional color, type hints, sorting of dict keys,
        and string truncation for readability.

        Behavior
        --------
        * Dicts: 'key: value' on a single line when value is scalar; nested values expand with indentation.
        * Lists: '[idx]: value' on a single line when scalar; nested values expand.
        * Tuples: '(idx): value' labels.
        * Sets: '{•}: value' labels (order not guaranteed; sorted when possible).
        * Scalars: printed directly; long strings are truncated to 'max_str' characters.

        :param obj: Object to be printed.
        :param indent: Current indentation level (internal for recursion).
        :param sort_keys: Sort dictionary keys alphabetically (safe if keys are comparable).
        :param show_types: Append the value's type in a dim style, e.g., '(str)'.
        :param max_str: Truncate strings longer than this length with an ellipsis.
        :param stream: Output stream; defaults to sys.stdout.

        Notes
        -----
        * Colors automatically fall back to plain text if ANSI is not supported.
        * Errors are logged using 'logging.error(..., exc_info=True)'.
        """
		enable_color = self._supports_color(stream, self._use_color_flag)
		orig_palette = self.palette
		try:
			if not enable_color:
				self.palette = {k: "" for k in orig_palette}

			# Mapping
			if isinstance(obj, _Mapping):
				self._print_mapping(
					obj, indent=indent, sort_keys=sort_keys, show_types=show_types,
					max_str=max_str, stream=stream
				)
				return

			# Iterable
			if isinstance(obj, _Iterable) and not isinstance(obj, (str, bytes, dict)):
				self._print_iterable(
					obj, indent=indent, show_types=show_types,
					max_str=max_str, stream=stream
				)
				return

			# Scalar
			self._print_scalar(
				obj, pad=self._pad(indent), max_str=max_str, show_types=show_types,
				stream=stream
			)

		except Exception as e:
			LOG.error(f"Error while printing object: {e}", exc_info=True)
			raise
		finally:
			self.palette = orig_palette

options: members: true show_root_heading: true heading_level: 2

Base helpers

format_interval(start_time, end_time, *, ms=False)

Format the elapsed time between two timestamps into a readable string.

Parameters:

Name Type Description Default
start_time float

Start time in seconds (e.g., from time.perf_counter()).

required
end_time float

End time in seconds.

required
ms bool

When True, append milliseconds as well.

False

Returns:

Type Description
str

Human-friendly time span, e.g. "1 h 02 min 03 s" or "45 s 120 ms" when ms=True.

Source code in src/sciwork/console/base.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def format_interval(start_time: float, end_time: float, *, ms: bool = False) -> str:
	"""
	Format the elapsed time between two timestamps into a readable string.

	:param start_time: Start time in seconds (e.g., from ``time.perf_counter()``).
	:param end_time: End time in seconds.
	:param ms: When ``True``, append milliseconds as well.
	:return: Human-friendly time span, e.g. ``"1 h 02 min 03 s"`` or
			``"45 s 120 ms"`` when ``ms=True``.
	"""
	interval = max(0.0, end_time - start_time)

	whole_seconds = int(interval)
	milliseconds = int(round((interval - whole_seconds) * 1000))

	hours, rem = divmod(whole_seconds, 3600)
	minutes, seconds = divmod(rem, 60)

	parts: list[str] = []
	if hours > 0:
		parts.append(f"{int(hours)} h")
	if minutes > 0:
		parts.append(f"{int(minutes)} min")
	if seconds > 0 or not parts:
		parts.append(f"{int(seconds)} s")
	if ms:
		parts.append(f"{milliseconds} ms")
	return " ".join(parts)

next_dots(current_count, *, max_dots=5, stream=sys.stdout, prefix='', suffix='')

Print a simple dot-loading animation step and return the updated count.

The function prints one dot each call until max_dots is reached, then clears the line segment and starts over.

prefix and suffix are optional strings that are printed before and after and are visually static.

Parameters:

Name Type Description Default
current_count int

Current number of dots already printed in the active cycle.

required
max_dots int

Maximum dots before resetting. Defaults to 5.

5
stream _Stream

Destination stream; defaults to sys.stdout.

stdout
prefix str

Optional text/ANSI printed before the dots.

''
suffix str

Optional text/ANSI printed after the dots.

''

Returns:

Type Description
int

Updated dot counter to be fed back into the next call.

Source code in src/sciwork/console/base.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def next_dots(
		current_count: int,
		*,
		max_dots: int = 5,
		stream: _Stream = sys.stdout,
		prefix: str = "",
		suffix: str = ""
) -> int:
	"""
	Print a simple dot-loading animation step and return the updated count.

	The function prints one dot each call until ``max_dots`` is reached, then
	clears the line segment and starts over.

	``prefix`` and ``suffix`` are optional strings that are printed before and after
	and are visually static.

	:param current_count: Current number of dots *already* printed in the active cycle.
	:param max_dots: Maximum dots before resetting. Defaults to 5.
	:param stream: Destination stream; defaults to ``sys.stdout``.
	:param prefix: Optional text/ANSI printed before the dots.
	:param suffix: Optional text/ANSI printed after the dots.
	:return: Updated dot counter to be fed back into the next call.
	"""
	n = max(0, min(current_count, max_dots - 1))
	dots = "." * (n + 1)
	pad = " " * (max_dots - (n + 1))
	print(f"\r{prefix}{dots}{pad}{suffix}", end="", flush=True, file=stream)
	return 0 if n + 1 >= max_dots else (n + 1)

print_rule(times=1, *, prespace=True, char='-', width=None, stream=sys.stdout, prefix='', suffix='')

Print a horizontal rule (optionally multiple times).

Parameters:

Name Type Description Default
times int

How many lines to print. Defaults to 1.

1
prespace bool

Print a blank line before the rules. Defaults to True.

True
char str

Single character used to build the rule. Defaults to "-".

'-'
width Optional[int]

Target width. If omitted, the terminal width is detected (fallback 100).

None
stream _Stream

Destination stream; defaults to sys.stdout.

stdout
prefix str

Optional string printed before the rule (e.g., ANSI color).

''
suffix str

Optional string printed after the rule (e.g., reset color).

''

Returns:

Type Description
int

The width that was used for the rule.

Source code in src/sciwork/console/base.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def print_rule(
		times: int = 1,
		*,
		prespace: bool = True,
		char: str = "-",
		width: Optional[int] = None,
		stream: _Stream = sys.stdout,
		prefix: str = "",
		suffix: str = ""
) -> int:
	"""
	Print a horizontal rule (optionally multiple times).

	:param times: How many lines to print. Defaults to 1.
	:param prespace: Print a blank line before the rules. Defaults to ``True``.
	:param char: Single character used to build the rule. Defaults to ``"-"``.
	:param width: Target width. If omitted, the terminal width is detected (fallback 100).
	:param stream: Destination stream; defaults to ``sys.stdout``.
	:param prefix: Optional string printed before the rule (e.g., ANSI color).
	:param suffix: Optional string printed after the rule (e.g., reset color).
	:return: The width that was used for the rule.
	"""
	if prespace:
		print("", file=stream)

	if not width:
		width = shutil.get_terminal_size(fallback=(100, 24)).columns
		width = max(20, width)

	line = char * width
	for _ in range(max(1, times)):
		print(f"{prefix}{line}{suffix}", file=stream)
	return width

options: members: true show_root_heading: true heading_level: 2

Bootstrap

ensure_ansi_colors(*, prefer='project', overwrite=False)

Ensure an ansi_colors.json exists and return its absolute path.

The file is created using a minimal default palette unless it already exists (unless overwrite=True). By default, it is placed under <cwd>/sciwork/configs/ansi_colors.json; pass prefer='user' to use a per-user config location (~/.config/sciwork/ or %APPDATA%\sciwork\ on Windows).

Parameters:

Name Type Description Default
prefer Literal['project', 'user']

Where to create/read the file ('project' or 'user').

'project'
overwrite bool

When True, rewrite the file even if it exists.

False

Returns:

Type Description
Path

Absolute path to the JSON file.

Raises:

Type Description
OSError

On filesystem errors when creating directories or writing the file.

Source code in src/sciwork/console/bootstrap.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def ensure_ansi_colors(
		*,
		prefer: Literal["project", "user"] = "project",
		overwrite: bool = False,
) -> Path:
	"""
	Ensure an ``ansi_colors.json`` exists and return its absolute path.

	The file is created using a minimal default palette unless it already exists
	(unless ``overwrite=True``). By default, it is placed under
	``<cwd>/sciwork/configs/ansi_colors.json``; pass ``prefer='user'`` to use a
	per-user config location (``~/.config/sciwork/`` or ``%APPDATA%\\sciwork\\`` on Windows).

	:param prefer: Where to create/read the file (``'project'`` or ``'user'``).
	:param overwrite: When ``True``, rewrite the file even if it exists.
	:return: Absolute path to the JSON file.
	:raises OSError: On filesystem errors when creating directories or writing the file.
	"""
	path = bootstrap_json_file(
		name="ansi_colors.json",
		payload=DEFAULT_ANSI_THEME,
		prefer=prefer,
		app="sciwork",
		overwrite=overwrite
	)
	LOG.info("ANSI theme ready at: %s", path)
	return path

options: members: true show_root_heading: true heading_level: 2