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

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.

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 and 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 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) 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 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) 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) 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.

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:

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.

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:

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.

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:

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.

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:

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.
  • 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 (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) 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →