"""Experimental features and settings for tyro.
This module contains experimental features and settings that may change or be removed
in future versions. Use with caution in production code.
Note: _experimental_options is exported in tyro.__init__ and should be accessed as:
tyro._experimental_options
"""
from __future__ import annotations
import contextlib
import os
import sys
import time
from typing import Any, Literal
from typing_extensions import TypedDict
from . import _fmtlib as fmt
[docs]
class ExperimentalOptionsDict(TypedDict):
"""Options for experimental tyro features.
Attributes:
enable_timing: Enable timing output for performance benchmarking.
backend: Backend to use for parsing ("argparse" or "tyro").
utf8_boxes: Enable UTF-8 box drawing characters in formatted output.
ansi_codes: Enable ANSI color codes in formatted output.
global_markers: Comma-separated names of :mod:`tyro.conf` markers to
apply globally to every :func:`tyro.cli` call, in addition to any
markers passed via ``config=``. Primarily useful for debugging, for
example ``PYTHON_TYRO_GLOBAL_MARKERS=FlagConversionOff,ShowSourcePath``.
"""
enable_timing: bool
backend: Literal["argparse", "tyro"]
utf8_boxes: bool
ansi_codes: bool
global_markers: str
[docs]
@contextlib.contextmanager
def timing_context(name: str):
"""Context manager to time a block of code."""
if not _experimental_options["enable_timing"]:
yield
return
start_time = time.perf_counter()
yield
end_time = time.perf_counter()
elapsed_time = end_time - start_time
print(f"{name} took {elapsed_time:.4f} seconds", file=sys.stderr, flush=True)
def _read_option(str_name: str, typ: Any, default: Any) -> Any: # pragma: no cover
if str_name in os.environ:
from .constructors import (
ConstructorRegistry,
PrimitiveTypeInfo,
UnsupportedTypeAnnotationError,
)
spec = ConstructorRegistry.get_primitive_spec(
PrimitiveTypeInfo.make(typ, set())
)
assert not isinstance(spec, UnsupportedTypeAnnotationError)
value = os.environ[str_name]
if spec.choices is not None:
assert value in spec.choices, (
f"{str_name}={value} not in choices {spec.choices}"
)
return spec.instance_from_str([value])
return default
# Global experimental options dictionary.
_experimental_options: ExperimentalOptionsDict = {
"enable_timing": _read_option("PYTHON_TYRO_ENABLE_TIMING", bool, False),
"backend": _read_option("PYTHON_TYRO_BACKEND", Literal["argparse", "tyro"], "tyro"),
"utf8_boxes": _read_option("PYTHON_TYRO_UTF8_BOXES", bool, True),
"ansi_codes": _read_option("PYTHON_TYRO_ANSI_CODES", bool, True),
"global_markers": _read_option("PYTHON_TYRO_GLOBAL_MARKERS", str, ""),
}
[docs]
def get_global_markers() -> tuple[Any, ...]:
"""Resolve the ``global_markers`` experimental option into marker objects.
The option is a comma-separated string of marker names that exist in
:mod:`tyro.conf` (e.g. ``"FlagConversionOff,ShowSourcePath"``). Each name is
looked up via :func:`getattr` on the :mod:`tyro.conf` module. Returns an
empty tuple when the option is unset.
"""
markers_str = _experimental_options["global_markers"]
if not markers_str:
return ()
# Imported lazily to avoid a circular import at module load time.
from . import conf
from .conf import _markers
markers = []
for name in markers_str.split(","):
name = name.strip()
if name == "":
continue
marker = getattr(conf, name, None)
# Reject both unknown names and names that resolve to non-marker members
# of `tyro.conf` (e.g. `arg`, `configure`), which would otherwise be
# silently ignored.
if not isinstance(marker, _markers._Marker):
raise ValueError(
f"Unknown marker {name!r} in the `global_markers` option "
"(PYTHON_TYRO_GLOBAL_MARKERS). Expected the name of a marker in "
"`tyro.conf`."
)
markers.append(marker)
return tuple(markers)
# TODO: revisit global.
ACCENT_COLOR: fmt.AnsiAttribute = "white"
[docs]
def set_accent_color(
accent_color: Literal[
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"bright_black",
"bright_red",
"bright_green",
"bright_yellow",
"bright_blue",
"bright_magenta",
"bright_cyan",
"bright_white",
]
| None,
) -> None:
"""Set an accent color to use in help messages. Experimental."""
global ACCENT_COLOR
ACCENT_COLOR = accent_color if accent_color is not None else "white"