Models

Structured parameters let one Python object expand into several CLI flags.

Interfacy can expand:

  • dataclasses

  • Pydantic models

  • plain classes with typed __init__ parameters

Dataclasses

from dataclasses import dataclass
from interfacy import Interfacy


@dataclass
class Address:
    city: str
    postal_code: int


@dataclass
class User:
    name: str
    age: int
    address: Address | None = None


def greet(user: User) -> str:
    return f"Hello {user.name}, age {user.age}"


Interfacy(print_result=True).run(greet)
$ python app.py --user.name Ada --user.age 32
Hello Ada, age 32

Nested fields use dot-separated flags by default:

$ python app.py \
    --user.name Ada \
    --user.age 32 \
    --user.address.city London \
    --user.address.postal-code 12345

The callable receives a real User instance.

Optional models

An optional model becomes None when none of its fields are provided.

def maybe_user(user: User | None = None) -> str:
    return "none" if user is None else user.name

If any nested field is provided, Interfacy reconstructs the model and validates required fields.

Model defaults

If a model parameter has a default instance, Interfacy starts with that default and merges CLI overrides into it.

DEFAULT_USER = User(name="guest", age=0)


def greet(user: User = DEFAULT_USER) -> str:
    return user.name
$ python app.py --user.name Ada

Plain classes

A plain class can be expanded when its initializer is typed.

class Server:
    def __init__(self, host: str, port: int = 8000) -> None:
        self.host = host
        self.port = port


def connect(server: Server) -> None:
    ...
$ python app.py --server.host localhost --server.port 9000

Pydantic models

Pydantic models are expanded using their fields and reconstructed before execution. Pydantic validation still belongs to the model.

Configuration

Model expansion is enabled by default.

Interfacy(expand_model_params=False).run(greet)

Limit nesting with model_expansion_max_depth:

Interfacy(model_expansion_max_depth=2).run(greet)

Change the nested separator with a flag strategy:

from interfacy.naming import DefaultFlagStrategy

Interfacy(
    flag_strategy=DefaultFlagStrategy(nested_separator="__"),
).run(greet)
$ python app.py --user__name Ada --user__age 32