# How to Use the Select Parameter Type in Hypster for Configuration Choices

> Learn to use Hypster's select parameter type to create configuration choices from lists or dictionaries. Validate selections and easily override defaults with hp.select.

- Repository: [Gilad Rubin/hypster](https://github.com/gilad-rubin/hypster)
- Tags: how-to-guide
- Published: 2026-03-02

---

**Use `hp.select(options, name="param", default="value")` to declare a configuration choice that accepts both list and dictionary inputs, validates selections against allowed keys, and supports external overrides through the `instantiate()` values dictionary.**

The `select` parameter type in Hypster enables declarative configuration choices within your Python functions. As implemented in the `gilad-rubin/hypster` repository, this feature provides a type-safe mechanism for defining discrete options with support for key-value mapping, strict validation, and hierarchical overrides via the `HP` class interface.

## Declaring Single Selections with Simple Lists

The most common use of the `select` parameter type in Hypster involves passing a list of valid options. When you call `hp.select()` inside a configuration function, you must provide a `name` parameter to enable external overrides.

```python
from hypster import HP, instantiate

def cfg(hp: HP) -> str:
    # Choose between "a", "b", "c". Default is "a".

    return hp.select(["a", "b", "c"], name="choice", default="a")

result = instantiate(cfg)               # → "a"

result = instantiate(cfg, values={"choice": "b"})  # → "b"

```

If you omit the `default` parameter, Hypster automatically uses the first element of the options list as the default value. The `name` parameter is required because it serves as the key for the `values` dictionary passed to `instantiate()`.

## The Internal Pipeline: How hp.select Processes Choices

Under the hood, the `select` parameter type follows a structured pipeline defined in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py) and [`src/hypster/hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp_calls.py). Understanding this flow helps debug configuration issues and leverage advanced features.

### Options Normalization with OptionsAdapter

First, the `OptionsAdapter` class (located at [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py) lines 21-45) processes the user-provided `options` argument. This adapter handles both list and dictionary inputs, creating two parallel structures:

- `option_keys`: The raw keys available for selection (strings for lists, keys for dicts)
- `option_map`: A mapping from keys to final returned values (identity mapping for lists, explicit mapping for dictionaries)

### Specification Creation

The `HP._select` method (lines 511-523 in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py)) builds a `SelectSingleSpec` dataclass that bundles the parameter `name`, processed `options`, `default` value, and the `options_only` boolean flag. This specification object encapsulates all metadata needed for validation and resolution.

### Execution and Validation Flow

The specification travels through `_execute_select_single` (lines 319-326), which delegates to `_handle_select_single`. During execution, three critical validation steps occur:

1. **Name Validation**: `SelectValidator.validate_name` (in [`src/hypster/hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp_calls.py) lines 106-115) guarantees that a `name` is supplied, which is required for the override mechanism to function.

2. **Value Retrieval**: The `_get_value_for_param` method (lines 68-96 in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py)) searches for user-provided values under either the plain parameter name or its fully-qualified nested path. If no override exists, it falls back to the resolved default from `OptionsAdapter`.

3. **Value Validation**: `SelectValidator.validate_value` (lines 116-124 in [`src/hypster/hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp_calls.py)) checks the supplied key against `option_keys`. If the key is not present and `options_only=True`, it raises a clear `ValueError`; otherwise, the value passes through for flexible configurations.

Finally, `_handle_select_single` (lines 109-117) maps the validated key through `option_map` to return the final value, enabling dictionary-based lookups to return complex objects while maintaining simple string keys for the API.

## Enforcing Validated Selections with options_only

By default, `hp.select` allows any value to pass through if provided via the `values` dictionary, which permits flexible overrides but risks silent typos. Set `options_only=True` to restrict inputs strictly to the declared options.

```python
def cfg_strict(hp: HP) -> str:
    return hp.select(
        ["red", "green", "blue"],
        name="color",
        default="red",
        options_only=True,      # disallow unknown values

    )

```

Attempting to pass an invalid option raises an immediate error:

```python
instantiate(cfg_strict, values={"color": "yellow"})

# ValueError: 'yellow' not in allowed options. Available: ['red', 'green', 'blue']

```

This validation occurs in `SelectValidator.validate_value`, providing safety against configuration drift while still allowing free-form values when `options_only` remains `False` (the default).

## Mapping Short Keys to Rich Configuration Values

The `select` parameter type in Hypster accepts dictionaries, enabling you to expose human-readable keys to users while returning complex internal values. This pattern is implemented through the `option_map` structure in `OptionsAdapter`.

```python
def cfg_dict(hp: HP) -> str:
    # Keys are short aliases; values are the real model IDs.

    return hp.select(
        {"fast": "gpt-4o-mini", "smart": "gpt-4"},
        name="model",
        default="fast",
    )

```

When `instantiate` processes this configuration, it validates against the keys `"fast"` and `"smart"`, but returns the mapped values:

```python
instantiate(cfg_dict)                         # → "gpt-4o-mini"

instantiate(cfg_dict, values={"model": "smart"})  # → "gpt-4"

```

This architecture allows you to change implementation details (like exact model identifiers) without modifying the user-facing API keys.

## Working with Complex Objects, None, and Tuples

Dictionary mappings support any value type, including `None`, tuples, or nested dictionaries. This flexibility lets you embed complete configuration objects directly in the options mapping.

```python
def cfg_complex(hp: HP) -> dict:
    return hp.select(
        {
            "small": {"name": "gpt-3.5-turbo", "max_tokens": 4096},
            "large": {"name": "gpt-4", "max_tokens": 8192},
        },
        name="model",
        default="small",
    )

def cfg_none_tuple(hp: HP) -> dict:
    tokenizer = hp.select(
        {"none": None, "basic": "basic_tokenizer"},
        name="tokenizer",
        default="none",
    )
    ngram = hp.select(
        {"unigram": (1, 1), "bigram": (1, 2), "trigram": (1, 3)},
        name="ngram_range",
        default="bigram",
    )
    return {"tokenizer": tokenizer, "ngram_range": ngram}

```

Execution returns the actual objects, not just string keys:

```python
instantiate(cfg_complex)  

# → {'name': 'gpt-3.5-turbo', 'max_tokens': 4096}

instantiate(cfg_none_tuple, values={"tokenizer": "basic", "ngram_range": "trigram"})

# → {'tokenizer': 'basic_tokenizer', 'ngram_range': (1, 3)}

```

## Selecting Multiple Options with hp.multi_select

For scenarios requiring multiple selections, Hypster provides `hp.multi_select`, which follows an identical validation pipeline but returns a list of chosen values instead of a single item.

```python
def cfg_multi(hp: HP) -> list:
    return hp.multi_select(
        ["apple", "banana", "cherry"],
        name="fruits",
        default=["apple", "banana"],
        options_only=True,
    )

```

The execution flow mirrors `hp.select`, utilizing the same `OptionsAdapter` and `SelectValidator` classes, but aggregates results into a list:

```python
instantiate(cfg_multi)                     # → ['apple', 'banana']

instantiate(cfg_multi, values={"fruits": ["cherry"]})  # → ['cherry']

```

## Summary

- **`hp.select`** declares single choices from lists or dictionaries, requiring a `name` parameter for override support as implemented in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py).
- **`OptionsAdapter`** (lines 21-45) normalizes inputs into `option_keys` and `option_map`, enabling both simple lists and rich key-value mappings.
- **`options_only=True`** enforces strict validation in `SelectValidator.validate_value`, preventing typos and invalid selections.
- **Dictionary options** allow human-readable keys to map to complex objects, `None` values, or tuples without changing the external API.
- **`hp.multi_select`** provides identical semantics for multiple selections, returning lists of validated values.
- **Validation pipeline**: `SelectValidator.validate_name` ensures name presence, while `_get_value_for_param` handles nested path resolution and default fallback.

## Frequently Asked Questions

### What happens if I don't provide a name parameter to hp.select?

The `SelectValidator.validate_name` method in [`src/hypster/hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp_calls.py) (lines 106-115) raises a validation error if the `name` parameter is missing or empty. This requirement exists because the `name` serves as the key for external overrides via `instantiate(values={...})`, and without it, the configuration cannot be properly addressed or overridden.

### Can I use hp.select with options that are not strings?

Yes. The `OptionsAdapter` class processes any hashable keys in dictionaries, including integers, tuples, or enums. The values can be any Python object, including `None`, dictionaries, or class instances. The validation logic in `SelectValidator.validate_value` checks key membership in `option_keys`, not the value types.

### How does hp.select handle nested configuration paths?

The `_get_value_for_param` method (lines 68-96 in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py)) supports fully-qualified nested paths using dot notation (e.g., `parent.child.choice`). When resolving values, it checks both the plain parameter name and its hierarchical path, enabling deep configuration structures without additional boilerplate while maintaining the same override semantics.

### What is the difference between hp.select and hp.multi_select?

While both use the same `OptionsAdapter` and `SelectValidator` infrastructure, `hp.select` returns a single value (the mapped result for one key), whereas `hp.multi_select` returns a list of values for multiple selected keys. The `multi_select` implementation processes each selected key through the same validation and mapping pipeline but aggregates the results into a list rather than returning a scalar.