.. Comment: this file is automatically generated by `update_example_docs.py`.
   It should not be modified manually.

.. _example-category-custom_constructors:

Custom constructors
===================

:func:`tyro.cli` aims for comprehensive support of standard Python type
constructs. It can still, however, be useful to extend the set of suported
types.

We provide two complementary approaches for doing so:

- :mod:`tyro.conf` provides a simple API for specifying custom constructor
  functions.
- :mod:`tyro.constructors` provides a more flexible API for defining behavior
  for different types. There are two categories of types: *primitive* types are
  instantiated from a single commandline argument, while *struct* types are
  broken down into multiple arguments.

.. warning::

    Custom constructors are useful, but can be verbose and require care. We
    recommend using them sparingly.


.. _example-01_simple_constructors:

Simple Constructors
-------------------

For simple custom constructors, we can pass a constructor function into
:func:`tyro.conf.arg` or :func:`tyro.conf.subcommand`. Arguments for will be
generated by parsing the signature of the constructor function.

In this example, we use this pattern to define custom behavior for
instantiating a NumPy array.


.. code-block:: python
    :linenos:

    # 01_simple_constructors.py
    from typing import Literal

    import numpy as np
    from typing_extensions import Annotated

    import tyro

    def construct_array(
        values: tuple[float, ...], dtype: Literal["float32", "float64"] = "float64"
    ) -> np.ndarray:
        """A custom constructor for 1D NumPy arrays."""
        return np.array(
            values,
            dtype={"float32": np.float32, "float64": np.float64}[dtype],
        )

    def main(
        # We can specify a custom constructor for an argument in `tyro.conf.arg()`.
        array: Annotated[np.ndarray, tyro.conf.arg(constructor=construct_array)],
    ) -> None:
        print(f"{array=}")

    if __name__ == "__main__":
        tyro.cli(main)




.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./01_simple_constructors.py --help</strong>
    <span style="font-weight: bold">usage</span>: 01_simple_constructors.py [-h] --array.values <span style="font-weight: bold">[FLOAT</span>
    <span style="font-weight: bold">                                 [FLOAT ...]]</span> [--array.dtype 
    <span style="font-weight: bold">{float32,float64}</span>]
    
    <span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> options </span><span style="font-weight: lighter">────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
    <span style="font-weight: lighter">│</span> -h, --help        <span style="font-weight: lighter">show this help message and exit</span> <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">╰───────────────────────────────────────────────────╯</span>
    <span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> array options </span><span style="font-weight: lighter">──────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
    <span style="font-weight: lighter">│</span> <span style="font-weight: bold">A custom constructor for 1D NumPy arrays.        </span> <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> <span style="font-weight: lighter">─────────────────────────────────────────        </span> <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --array.values <span style="font-weight: bold">[FLOAT [FLOAT ...]]</span>                <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span>                   <span style="font-weight: bold; color: #e60000">(required)</span>                      <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --array.dtype <span style="font-weight: bold">{float32,float64}</span>                   <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span>                   <span style="color: #008080">(default: float64)</span>              <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">╰───────────────────────────────────────────────────╯</span>
    </pre>



.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./01_simple_constructors.py --array.values 1 2 3</strong>
    array=array([1., 2., 3.])
    </pre>



.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./01_simple_constructors.py --array.values 1 2 3 4 5 --array.dtype float32</strong>
    array=array([1., 2., 3., 4., 5.], dtype=float32)
    </pre>
.. _example-02_primitive_annotation:

Custom Primitive
----------------

In this example, we use :mod:`tyro.constructors` to attach a primitive
constructor via a runtime annotation.


.. code-block:: python
    :linenos:

    # 02_primitive_annotation.py
    import json

    from typing_extensions import Annotated

    import tyro

    # A dictionary type, but `tyro` will expect a JSON string from the CLI.
    JsonDict = Annotated[
        dict,
        tyro.constructors.PrimitiveConstructorSpec(
            # Number of arguments to consume.
            nargs=1,
            # Argument name in usage messages.
            metavar="JSON",
            # Convert a list of strings to an instance. The length of the list
            # should match `nargs`.
            instance_from_str=lambda args: json.loads(args[0]),
            # Check if an instance is of the expected type. This is only used for
            # helptext formatting in the presence of union types.
            is_instance=lambda instance: isinstance(instance, dict),
            # Convert an instance to a list of strings. This is used for handling
            # default values that are set in Python. The length of the list should
            # match `nargs`.
            str_from_instance=lambda instance: [json.dumps(instance)],
        ),
    ]

    def main(
        dict1: JsonDict,
        dict2: JsonDict = {"default": None},
    ) -> None:
        print(f"{dict1=}")
        print(f"{dict2=}")

    if __name__ == "__main__":
        tyro.cli(main)




.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./02_primitive_annotation.py --help</strong>
    <span style="font-weight: bold">usage</span>: 02_primitive_annotation.py [-h] --dict1 <span style="font-weight: bold">JSON</span> [--dict2 <span style="font-weight: bold">JSON</span>]
    
    <span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> options </span><span style="font-weight: lighter">──────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
    <span style="font-weight: lighter">│</span> -h, --help          <span style="font-weight: lighter">show this help message and exit</span> <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --dict1 <span style="font-weight: bold">JSON</span>        <span style="font-weight: bold; color: #e60000">(required)</span>                      <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --dict2 <span style="font-weight: bold">JSON</span>        <span style="color: #008080">(default: '{"default": null}')</span>  <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">╰─────────────────────────────────────────────────────╯</span>
    </pre>



.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./02_primitive_annotation.py --dict1 '{"hello": "world"}'</strong>
    dict1={'hello': 'world'}
    dict2={'default': None}
    </pre>



.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./02_primitive_annotation.py --dict1 '{"hello": "world"}' --dict2 '{"hello": "world"}'</strong>
    dict1={'hello': 'world'}
    dict2={'hello': 'world'}
    </pre>
.. _example-03_primitive_registry:

Custom Primitive (Registry)
---------------------------

In this example, we use a :class:`tyro.constructors.ConstructorRegistry` to
define a rule that applies to all types that match ``dict[str, Any]``.


.. code-block:: python
    :linenos:

    # 03_primitive_registry.py
    import json
    from typing import Any

    import tyro

    # Create a custom registry, which stores constructor rules.
    custom_registry = tyro.constructors.ConstructorRegistry()

    # Define a rule that applies to all types that match `dict[str, Any]`.
    @custom_registry.primitive_rule
    def _(
        type_info: tyro.constructors.PrimitiveTypeInfo,
    ) -> tyro.constructors.PrimitiveConstructorSpec | None:
        # We return `None` if the rule does not apply.
        if type_info.type != dict[str, Any]:
            return None

        # If the rule applies, we return the constructor spec.
        return tyro.constructors.PrimitiveConstructorSpec(
            nargs=1,
            metavar="JSON",
            instance_from_str=lambda args: json.loads(args[0]),
            is_instance=lambda instance: isinstance(instance, dict),
            str_from_instance=lambda instance: [json.dumps(instance)],
        )

    def main(
        dict1: dict[str, Any],
        dict2: dict[str, Any] = {"default": None},
    ) -> None:
        """A function with two arguments, which can be populated from the CLI via JSON."""
        print(f"{dict1=}")
        print(f"{dict2=}")

    if __name__ == "__main__":
        # To activate a custom registry, we should use it as a context manager.
        with custom_registry:
            tyro.cli(main)




.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./03_primitive_registry.py --help</strong>
    <span style="font-weight: bold">usage</span>: 03_primitive_registry.py [-h] --dict1 <span style="font-weight: bold">JSON</span> [--dict2 <span style="font-weight: bold">JSON</span>]
    
    A function with two arguments, which can be populated from the CLI via JSON.
    
    <span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> options </span><span style="font-weight: lighter">──────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
    <span style="font-weight: lighter">│</span> -h, --help          <span style="font-weight: lighter">show this help message and exit</span> <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --dict1 <span style="font-weight: bold">JSON</span>        <span style="font-weight: bold; color: #e60000">(required)</span>                      <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --dict2 <span style="font-weight: bold">JSON</span>        <span style="color: #008080">(default: '{"default": null}')</span>  <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">╰─────────────────────────────────────────────────────╯</span>
    </pre>



.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./03_primitive_registry.py --dict1 '{"hello": "world"}'</strong>
    dict1={'hello': 'world'}
    dict2={'default': None}
    </pre>



.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./03_primitive_registry.py --dict1 '{"hello": "world"}' --dict2 '{"hello": "world"}'</strong>
    dict1={'hello': 'world'}
    dict2={'hello': 'world'}
    </pre>
.. _example-04_struct_registry:

Custom Structs (Registry)
-------------------------

In this example, we use a :class:`tyro.constructors.ConstructorRegistry` to
add support for a custom type.

.. warning::

    This will be complicated!


.. code-block:: python
    :linenos:

    # 04_struct_registry.py
    import tyro

    # A custom type that we'll add support for to tyro.
    class Bounds:
        def __init__(self, lower: int, upper: int):
            self.bounds = (lower, upper)

        def __repr__(self) -> str:
            return f"(lower={self.bounds[0]}, upper={self.bounds[1]})"

    # Create a custom registry, which stores constructor rules.
    custom_registry = tyro.constructors.ConstructorRegistry()

    # Define a rule that applies to all types that match `Bounds`.
    @custom_registry.struct_rule
    def _(
        type_info: tyro.constructors.StructTypeInfo,
    ) -> tyro.constructors.StructConstructorSpec | None:
        # We return `None` if the rule does not apply.
        if type_info.type != Bounds:
            return None

        # We can extract the default value of the field from `type_info`.
        if isinstance(type_info.default, Bounds):
            # If the default value is a `Bounds` instance, we don't need to generate a constructor.
            default = (type_info.default.bounds[0], type_info.default.bounds[1])
            is_default_overridden = True
        else:
            # Otherwise, the default value is missing. We'll mark the child defaults as missing as well.
            assert type_info.default in (
                tyro.constructors.MISSING,
                tyro.constructors.MISSING_NONPROP,
            )
            default = (tyro.MISSING, tyro.MISSING)
            is_default_overridden = False

        # If the rule applies, we return the constructor spec.
        return tyro.constructors.StructConstructorSpec(
            # The instantiate function will be called with the fields as keyword arguments.
            instantiate=Bounds,
            fields=(
                tyro.constructors.StructFieldSpec(
                    name="lower",
                    type=int,
                    default=default[0],
                    is_default_overridden=is_default_overridden,
                    helptext="Lower bound." "",
                ),
                tyro.constructors.StructFieldSpec(
                    name="upper",
                    type=int,
                    default=default[1],
                    is_default_overridden=is_default_overridden,
                    helptext="Upper bound." "",
                ),
            ),
        )

    def main(
        bounds: Bounds,
        bounds_with_default: Bounds = Bounds(0, 100),
    ) -> None:
        """A function with two `Bounds` instances as input."""
        print(f"{bounds=}")
        print(f"{bounds_with_default=}")

    if __name__ == "__main__":
        # To activate a custom registry, we should use it as a context manager.
        with custom_registry:
            tyro.cli(main)




.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./04_struct_registry.py --help</strong>
    <span style="font-weight: bold">usage</span>: 04_struct_registry.py [-h] [OPTIONS]
    
    A function with two `Bounds` instances as input.
    
    <span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> options </span><span style="font-weight: lighter">──────────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
    <span style="font-weight: lighter">│</span> -h, --help              <span style="font-weight: lighter">show this help message and exit</span> <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">╰─────────────────────────────────────────────────────────╯</span>
    <span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> bounds options </span><span style="font-weight: lighter">───────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
    <span style="font-weight: lighter">│</span> --bounds.lower <span style="font-weight: bold">INT</span>      <span style="font-weight: lighter">Lower bound.</span> <span style="font-weight: bold; color: #e60000">(required)</span>         <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --bounds.upper <span style="font-weight: bold">INT</span>      <span style="font-weight: lighter">Upper bound.</span> <span style="font-weight: bold; color: #e60000">(required)</span>         <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">╰─────────────────────────────────────────────────────────╯</span>
    <span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> bounds-with-default options </span><span style="font-weight: lighter">──────────────────────────</span><span style="font-weight: lighter">─╮</span>
    <span style="font-weight: lighter">│</span> --bounds-with-default.lower <span style="font-weight: bold">INT</span>                         <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span>                         <span style="font-weight: lighter">Lower bound.</span> <span style="color: #008080">(default: 0)</span>       <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span> --bounds-with-default.upper <span style="font-weight: bold">INT</span>                         <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">│</span>                         <span style="font-weight: lighter">Upper bound.</span> <span style="color: #008080">(default: 100)</span>     <span style="font-weight: lighter">│</span>
    <span style="font-weight: lighter">╰─────────────────────────────────────────────────────────╯</span>
    </pre>



.. raw:: html

    <pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
    <strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./04_struct_registry.py --bounds.lower 5 --bounds.upper 10</strong>
    bounds=(lower=5, upper=10)
    bounds_with_default=(lower=0, upper=100)
    </pre>